openrvdas.logger.utils.record_parser_formats
Custom format definitions for RecordParser.
To use, import the 'extra_format_types' dict and pass to the parser:
import parse from logger.utils.record_parser_formats import extra_format_types
pattern = parse.compile(format=record_format, extra_types=extra_format_types) parsed_values = pattern.parse(record)
Why We Have This
We want to expand the default repertoire of the parse() function to be able to handle ints/floats/strings that might be omitted. To do that, we define some additional named types for it that we can use in format definitions.
These might be used, for example, in the following pattern where we might have either, both or neither of speed in knots and/or km/hour.
"{SpeedKt:of},N,{SpeedKm:of},K"
The recognized format types we add are: od = optional integer of = optional generalized float og = optional generalized number - also handles '#VALUE!' as None ow = optional sequence of letters, numbers, underscores os = optional sequence of any characters - will match everything on line
nlat = NMEA-formatted latitude or longitude, converted to decimal degrees
nc = any ASCII text that is not a comma ns = any ASCII text that is not an asterisk ("star")
See 'Custom Type Conversions' in https://pypi.org/project/parse/ for a discussion of how format types work.
TODO: allow device_type definitions to hand in their own format types.
1#!/usr/bin/env python3 2 3"""Custom format definitions for RecordParser. 4 5To use, import the 'extra_format_types' dict and pass to the parser: 6 7import parse 8from logger.utils.record_parser_formats import extra_format_types 9 10pattern = parse.compile(format=record_format, extra_types=extra_format_types) 11parsed_values = pattern.parse(record) 12 13# Why We Have This 14 15We want to expand the default repertoire of the parse() function to be 16able to handle ints/floats/strings that *might* be omitted. To do 17that, we define some additional named types for it that we can use in 18format definitions. 19 20These might be used, for example, in the following pattern where we 21might have either, both or neither of speed in knots and/or km/hour. 22 23 "{SpeedKt:of},N,{SpeedKm:of},K" 24 25The recognized format types we add are: 26 od = optional integer 27 of = optional generalized float 28 og = optional generalized number - also handles '#VALUE!' as None 29 ow = optional sequence of letters, numbers, underscores 30 os = optional sequence of any characters - will match everything on line 31 32 nlat = NMEA-formatted latitude or longitude, converted to decimal degrees 33 34 nc = any ASCII text that is not a comma 35 ns = any ASCII text that is not an asterisk ("star") 36 37See 'Custom Type Conversions' in https://pypi.org/project/parse/ for a 38discussion of how format types work. 39 40TODO: allow device_type definitions to hand in their own format types. 41""" 42import logging 43 44 45def optional_d(text): 46 """Method for parsing an 'optional' integer.""" 47 if text: 48 return int(text) 49 else: 50 return None 51 52 53optional_d.pattern = r'\s*[-+]?\d*' 54 55 56def optional_f(text): 57 """Method for parsing an 'optional' generalized float.""" 58 if text: 59 return float(text) 60 else: 61 return None 62 63 64optional_f.pattern = r'(\s*[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?|)' 65 66 67def optional_g(text): 68 """Method for parsing an 'optional' generalized number.""" 69 if text == '#VALUE!': 70 return None 71 if text: 72 return float(text) 73 else: 74 return None 75 76 77optional_g.pattern = r'(#VALUE!|\s*[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?|\d*)' 78 79 80def optional_w(text): 81 """Method for parsing an 'optional' letters/numbers/underscore 82 string.""" 83 if text: 84 return text 85 else: 86 return None 87 88 89optional_w.pattern = r'\w*' 90 91 92def optional_s(text): 93 """Method for parsing any sequence of zero or more characters. Will absorb 94 everything in the string. 95 """ 96 if text: 97 return text 98 else: 99 return '' 100 101 102optional_s.pattern = r'.*' 103 104 105def nmea_lat_lon(text): 106 """Method for parsing an NMEA latitude or longitude (DDDMM.MMMM) and 107 converting it into decimal degrees. Only handles the numeric part, not 108 any E/W or N/S component.""" 109 if text: 110 nmea_value = float(text) 111 normalized_value = nmea_value / 100 112 degrees = int(normalized_value) 113 if abs(degrees) >= 180.0: 114 logging.warning('Improper NMEA-style latitude/longitude: "%s"', text) 115 return None 116 fractional_degrees = (normalized_value - degrees) / 0.60 117 if abs(fractional_degrees) >= 1.0: 118 logging.warning('Improper NMEA-style latitude/longitude: "%s"', text) 119 return None 120 return degrees + fractional_degrees 121 else: 122 return None 123 124 125nmea_lat_lon.pattern = r'(\s*[-]?(\d+(\.\d*)?|\.\d+)?|)' 126 127 128def nmea_lat_lon_dir(text): 129 """Method for parsing an NMEA latitude or longitude (DDDMM.MMMM) along 130 with the hemisphere (E/W/N/S) and converting it into signed decimal 131 degrees. South and West are considered negative, North and East 132 positive. 133 """ 134 if text: 135 nmea_str, dir = text.split(',') 136 nmea_value = float(nmea_str) 137 normalized_value = nmea_value / 100 138 degrees = int(normalized_value) 139 if abs(degrees) >= 180.0: 140 logging.warning('Improper NMEA-style latitude/longitude: "%s"', text) 141 return None 142 fractional_degrees = (normalized_value - degrees) / 0.60 143 if abs(fractional_degrees) >= 1.0: 144 logging.warning('Improper NMEA-style latitude/longitude: "%s"', text) 145 return None 146 decimal_degrees = degrees + fractional_degrees 147 if dir in ['W', 'S']: 148 decimal_degrees = -decimal_degrees 149 return decimal_degrees 150 else: 151 return None 152 153 154nmea_lat_lon_dir.pattern = r'(\s*(\d+(\.\d*)?|\.\d+),[NEWS]?|)' 155 156 157def not_comma(text): 158 """Method for parsing a string (or anything) between commas 159 string.""" 160 if text: 161 return text 162 else: 163 return None 164 165 166not_comma.pattern = r'[^,]*' 167 168 169def not_star(text): 170 """Method for parsing a string (or anything) terminated by a "*" 171 """ 172 if text: 173 return text 174 else: 175 return None 176 177 178not_star.pattern = r'[^\*]*' 179 180 181extra_format_types = dict( 182 od=optional_d, 183 of=optional_f, 184 og=optional_g, 185 ow=optional_w, 186 os=optional_s, 187 188 nlat=nmea_lat_lon, 189 nlat_dir=nmea_lat_lon_dir, 190 191 nc=not_comma, 192 ns=not_star)
46def optional_d(text): 47 """Method for parsing an 'optional' integer.""" 48 if text: 49 return int(text) 50 else: 51 return None
Method for parsing an 'optional' integer.
57def optional_f(text): 58 """Method for parsing an 'optional' generalized float.""" 59 if text: 60 return float(text) 61 else: 62 return None
Method for parsing an 'optional' generalized float.
68def optional_g(text): 69 """Method for parsing an 'optional' generalized number.""" 70 if text == '#VALUE!': 71 return None 72 if text: 73 return float(text) 74 else: 75 return None
Method for parsing an 'optional' generalized number.
81def optional_w(text): 82 """Method for parsing an 'optional' letters/numbers/underscore 83 string.""" 84 if text: 85 return text 86 else: 87 return None
Method for parsing an 'optional' letters/numbers/underscore string.
93def optional_s(text): 94 """Method for parsing any sequence of zero or more characters. Will absorb 95 everything in the string. 96 """ 97 if text: 98 return text 99 else: 100 return ''
Method for parsing any sequence of zero or more characters. Will absorb everything in the string.
106def nmea_lat_lon(text): 107 """Method for parsing an NMEA latitude or longitude (DDDMM.MMMM) and 108 converting it into decimal degrees. Only handles the numeric part, not 109 any E/W or N/S component.""" 110 if text: 111 nmea_value = float(text) 112 normalized_value = nmea_value / 100 113 degrees = int(normalized_value) 114 if abs(degrees) >= 180.0: 115 logging.warning('Improper NMEA-style latitude/longitude: "%s"', text) 116 return None 117 fractional_degrees = (normalized_value - degrees) / 0.60 118 if abs(fractional_degrees) >= 1.0: 119 logging.warning('Improper NMEA-style latitude/longitude: "%s"', text) 120 return None 121 return degrees + fractional_degrees 122 else: 123 return None
Method for parsing an NMEA latitude or longitude (DDDMM.MMMM) and converting it into decimal degrees. Only handles the numeric part, not any E/W or N/S component.
129def nmea_lat_lon_dir(text): 130 """Method for parsing an NMEA latitude or longitude (DDDMM.MMMM) along 131 with the hemisphere (E/W/N/S) and converting it into signed decimal 132 degrees. South and West are considered negative, North and East 133 positive. 134 """ 135 if text: 136 nmea_str, dir = text.split(',') 137 nmea_value = float(nmea_str) 138 normalized_value = nmea_value / 100 139 degrees = int(normalized_value) 140 if abs(degrees) >= 180.0: 141 logging.warning('Improper NMEA-style latitude/longitude: "%s"', text) 142 return None 143 fractional_degrees = (normalized_value - degrees) / 0.60 144 if abs(fractional_degrees) >= 1.0: 145 logging.warning('Improper NMEA-style latitude/longitude: "%s"', text) 146 return None 147 decimal_degrees = degrees + fractional_degrees 148 if dir in ['W', 'S']: 149 decimal_degrees = -decimal_degrees 150 return decimal_degrees 151 else: 152 return None
Method for parsing an NMEA latitude or longitude (DDDMM.MMMM) along with the hemisphere (E/W/N/S) and converting it into signed decimal degrees. South and West are considered negative, North and East positive.
158def not_comma(text): 159 """Method for parsing a string (or anything) between commas 160 string.""" 161 if text: 162 return text 163 else: 164 return None
Method for parsing a string (or anything) between commas string.
170def not_star(text): 171 """Method for parsing a string (or anything) terminated by a "*" 172 """ 173 if text: 174 return text 175 else: 176 return None
Method for parsing a string (or anything) terminated by a "*"