es_sfgtools.novatel_tools.rangea_parser module
RANGEA ASCII Log Parser
This module provides Python functions to parse NovAtel RANGEA ASCII log strings into structured observation data, inspired by the Go-lang GNSS tools implementation using novatelascii.DeserializeRANGEA and observation.Epoch.
The RANGEA log contains GNSS pseudorange, carrier phase, Doppler, and C/N0 measurements for all tracked satellites across multiple constellations.
- class es_sfgtools.novatel_tools.rangea_parser.GNSSEpoch(*, time: datetime, gps_week: int, gps_seconds: float, satellites: Dict[Tuple[int, int], Satellite] = None, receiver_status: str = '', num_observations: int = 0)
Bases:
BaseModelA GNSS observation epoch containing all satellite measurements at one time.
This is the Python equivalent of Go’s observation.Epoch structure. An epoch represents all GNSS observations recorded at a single instant, typically at the receiver’s measurement rate (e.g., 1 Hz, 10 Hz).
- time
UTC timestamp of the epoch
- Type:
datetime.datetime
- gps_week
GPS week number
- Type:
int
- gps_seconds
Seconds into the GPS week
- Type:
float
- satellites
Dictionary mapping (system, prn) tuple to Satellite
- Type:
Dict[Tuple[int, int], es_sfgtools.novatel_tools.rangea_parser.Satellite]
- receiver_status
Raw receiver status word from header
- Type:
str
- num_observations
Total number of observation records
- Type:
int
- get_satellite(system: GNSSSystem, prn: int) Satellite | None
Get a satellite by system and PRN.
- get_systems() List[GNSSSystem]
Return list of GNSS systems present in this epoch.
- gps_seconds: float
- gps_week: int
- model_computed_fields = {'satellite_count': ComputedFieldInfo(wrapped_property=<property object>, return_type=<class 'int'>, alias=None, alias_priority=None, title=None, field_title_generator=None, description='Return the number of unique satellites in this epoch.', deprecated=None, examples=None, json_schema_extra=None, repr=True)}
A dictionary of computed field names and their corresponding ComputedFieldInfo objects.
- model_config = {'frozen': False}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_fields = {'gps_seconds': FieldInfo(annotation=float, required=True), 'gps_week': FieldInfo(annotation=int, required=True), 'num_observations': FieldInfo(annotation=int, required=False, default=0), 'receiver_status': FieldInfo(annotation=str, required=False, default=''), 'satellites': FieldInfo(annotation=Dict[Tuple[int, int], Satellite], required=False, default_factory=dict), 'time': FieldInfo(annotation=datetime, required=True)}
Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].
This replaces Model.__fields__ from Pydantic V1.
- num_observations: int
- receiver_status: str
- property satellite_count: int
Return the number of unique satellites in this epoch.
- time: datetime
- class es_sfgtools.novatel_tools.rangea_parser.GNSSSystem(value)
Bases:
IntEnumGNSS constellation identifiers from NovAtel channel tracking status.
- BEIDOU = 5
- GALILEO = 3
- GLONASS = 1
- GPS = 0
- NAVIC = 7
- QZSS = 6
- SBAS = 2
- class es_sfgtools.novatel_tools.rangea_parser.Observation(*, signal_type: int, pseudorange: float, pseudorange_std: float, carrier_phase: float, carrier_phase_std: float, doppler: float, cn0: float, locktime: float, tracking_status: int, half_cycle_ambiguity: bool = False, phase_lock: bool = True, code_lock: bool = True, parity_known: bool = True)
Bases:
BaseModelA single GNSS observation for one signal from one satellite.
This corresponds to a single observation record within a RANGEA message, containing pseudorange, carrier phase, Doppler, and signal quality metrics.
- carrier_phase: float
- carrier_phase_std: float
- cn0: float
- code_lock: bool
- doppler: float
- half_cycle_ambiguity: bool
- locktime: float
- model_computed_fields = {}
A dictionary of computed field names and their corresponding ComputedFieldInfo objects.
- model_config = {'frozen': False}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_fields = {'carrier_phase': FieldInfo(annotation=float, required=True, title='Carrier Phase', description='Accumulated Doppler range (ADR) in cycles'), 'carrier_phase_std': FieldInfo(annotation=float, required=True, title='Carrier Phase Std', description='Carrier phase standard deviation in cycles'), 'cn0': FieldInfo(annotation=float, required=True, title='C/N0', description='Carrier-to-noise density ratio in dB-Hz'), 'code_lock': FieldInfo(annotation=bool, required=False, default=True, title='Code Lock', description='True if code is locked'), 'doppler': FieldInfo(annotation=float, required=True, title='Doppler', description='Doppler frequency shift in Hz'), 'half_cycle_ambiguity': FieldInfo(annotation=bool, required=False, default=False, title='Half Cycle Ambiguity', description='True if half-cycle ambiguity is present'), 'locktime': FieldInfo(annotation=float, required=True, title='Lock Time', description='Continuous tracking time in seconds'), 'parity_known': FieldInfo(annotation=bool, required=False, default=True, title='Parity Known', description='True if parity is known (for navigation data)'), 'phase_lock': FieldInfo(annotation=bool, required=False, default=True, title='Phase Lock', description='True if phase is locked'), 'pseudorange': FieldInfo(annotation=float, required=True, title='Pseudorange', description='Pseudorange measurement in meters'), 'pseudorange_std': FieldInfo(annotation=float, required=True, title='Pseudorange Std', description='Pseudorange standard deviation in meters'), 'signal_type': FieldInfo(annotation=int, required=True, title='Signal Type Identifier'), 'tracking_status': FieldInfo(annotation=int, required=True, title='Tracking Status', description='Raw 32-bit channel tracking status word')}
Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].
This replaces Model.__fields__ from Pydantic V1.
- parity_known: bool
- phase_lock: bool
- pseudorange: float
- pseudorange_std: float
- signal_type: int
- tracking_status: int
- class es_sfgtools.novatel_tools.rangea_parser.Satellite(*, system: GNSSSystem, prn: int, fcn: int = 0, observations: Dict[int, Observation] = None)
Bases:
BaseModelGNSS satellite with all its observations.
A satellite may have multiple observations for different signals (e.g., GPS satellite might have L1CA, L2C, and L5 observations).
- system
GNSS constellation (GPS, GLONASS, Galileo, etc.)
- prn
Satellite PRN number (or slot for GLONASS)
- Type:
int
- fcn
GLONASS frequency channel number (-7 to +6), 0 for other systems
- Type:
int
- observations
Dictionary mapping signal type to Observation
- Type:
Dict[int, es_sfgtools.novatel_tools.rangea_parser.Observation]
- add_observation(obs: Observation) None
Add an observation for a specific signal type.
- fcn: int
- model_computed_fields = {}
A dictionary of computed field names and their corresponding ComputedFieldInfo objects.
- model_config = {'frozen': False}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_fields = {'fcn': FieldInfo(annotation=int, required=False, default=0), 'observations': FieldInfo(annotation=Dict[int, Observation], required=False, default_factory=dict), 'prn': FieldInfo(annotation=int, required=True), 'system': FieldInfo(annotation=GNSSSystem, required=True)}
Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].
This replaces Model.__fields__ from Pydantic V1.
- observations: Dict[int, Observation]
- prn: int
- system: GNSSSystem
- class es_sfgtools.novatel_tools.rangea_parser.SignalType(value)
Bases:
IntEnumCommon GNSS signal types (simplified mapping).
- B1I = 0
- B2I = 2
- B3I = 6
- E1 = 2
- E5A = 12
- E5B = 17
- L1C = 17
- L1CA = 0
- L2C = 9
- L2P = 5
- L5Q = 14
- es_sfgtools.novatel_tools.rangea_parser.deserialize_rangea(rangea_string: str) GNSSEpoch
Parse a NovAtel RANGEA ASCII log string into an Epoch object.
- This function is the Python equivalent of the Go code:
rangea, err := novatelascii.DeserializeRANGEA(m.Data) epoch, err := rangea.SerializeGNSSEpoch(m.Time())
- RANGEA Format:
#RANGEA,<header>;num_obs,<obs1>,…,<obsN>*checksum
- Each observation has 10 fields:
prn, glo_freq, psr, psr_std, adr, adr_std, dopp, cn0, locktime, ch_tr_status
- Parameters:
rangea_string – Complete RANGEA ASCII log string including header and checksum
- Returns:
Epoch object containing all parsed satellite observations
- Raises:
ValueError – If the string cannot be parsed as a valid RANGEA message
Example
>>> rangea = "#RANGEA,USB2,0,73.5,FINESTEERING,2379,414835.000,..." >>> epoch = deserialize_rangea(rangea) >>> print(f"Epoch time: {epoch.time}, satellites: {epoch.satellite_count}")
- es_sfgtools.novatel_tools.rangea_parser.epoch_to_dict(epoch: GNSSEpoch) dict
Convert an Epoch object to a dictionary for serialization.
- Parameters:
epoch – Epoch object to convert
- Returns:
Dictionary representation suitable for JSON serialization
- es_sfgtools.novatel_tools.rangea_parser.extract_rangea_from_qcpin(source: str | Path) List[GNSSEpoch]
Extract and parse all RANGEA logs from a QC PIN file.
This function loads a QC PIN JSON file and searches through it for NOV_RANGE observations containing raw RANGEA strings, parses them into GNSSEpoch objects, and returns all unique epochs.
- The JSON structure is expected to have entries like:
- {
“interrogation”: {“observations”: {“NOV_RANGE”: {“raw”: “#RANGEA,…”, “time”: {…}}}}, “007BE1”: {“observations”: {“NOV_RANGE”: {“raw”: “#RANGEA,…”, “time”: {…}}}}, …
}
- Parameters:
source – Path to the QC PIN file in JSON format
- Returns:
List of unique GNSSEpoch objects, deduplicated by GPS week/seconds. Returns empty list if file cannot be read or contains no valid RANGEA logs.
Example
>>> epochs = extract_rangea_from_qcpin("/path/to/file.pin") >>> print(f"Found {len(epochs)} unique epochs")
- es_sfgtools.novatel_tools.rangea_parser.extract_rangea_strings_from_qcpin(source: str | Path) List[str]
Extract raw RANGEA strings from a QC PIN file.
This function loads a QC PIN JSON file and searches through it for NOV_RANGE observations containing raw RANGEA strings, returning a list of all found RANGEA strings without parsing them into epochs.
- Parameters:
source – Path to the QC PIN file in JSON format
- Returns:
List of raw RANGEA strings found in the file. Returns empty list if file cannot be read or contains no valid RANGEA logs.