Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 35 additions & 22 deletions gnss_tec/dtutils.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
"""Various utils to work with date/time."""
import datetime
from typing import List


def get_microsec(sec):
"""Return microsecond value from fractional part of the sec."""
def get_microsec(sec: float) -> float:
"""
Return microsecond value from fractional part of the sec.

Parameters
----------
sec : float
Seconds value that may contain fractional part

Returns
-------
float
Microsecond value calculated from fractional part
"""
microsec = (sec - int(sec)) * 1e+6
microsec = float("%.5f" % microsec)
return microsec
return round(microsec, 5)


def validate_epoch(epoch):
"""Return datetime.datetime object using values from epoch list.
def validate_epoch(epoch: List[int]) -> datetime.datetime:
"""
Return datetime.datetime object using values from epoch list.

Do some checks:
- sometimes the seconds or minutes value >= 60, to return datetime.datetime
Expand All @@ -20,32 +33,32 @@ def validate_epoch(epoch):

Parameters
----------
epoch : list
epoch = [year, month, day, hour, min, sec, microsec]
epoch : List[int]
List in format [year, month, day, hour, min, sec, microsec]

Returns
-------
datetime : datetime.datetime
datetime.datetime
Validate datetime object
"""
epoch = epoch[:]
# Create a copy to avoid modifying the original list
epoch = epoch.copy()

# YY -> YYYY
# Convert two-digit years to four-digit format
if epoch[0] < 100:
if epoch[0] >= 89:
epoch[0] += 1900
elif epoch[0] < 89:
epoch[0] += 2000

delta = datetime.timedelta(0)

# epoch[-2] - seconds; epoch[-3] - minutes
# we do all calculation in seconds so we use multiplier
for i, ier in [(-2, 1), (-3, 60)]:
if 60 <= epoch[i] <= 120:
sec = (epoch[i] - 59) * ier
delta += datetime.timedelta(seconds=sec)
epoch[i] = 59
time_delta = datetime.timedelta(0)

epoch = datetime.datetime(*epoch) + delta
# Process seconds (index: -2) and minutes (index: -3)
# Handle oversized seconds and minutes
for index, multiplier in [(-2, 1), (-3, 60)]:
if 60 <= epoch[index] <= 120:
sec = (epoch[index] - 59) * multiplier
time_delta += datetime.timedelta(seconds=sec)
epoch[index] = 59

return epoch
return datetime.datetime(*epoch) + time_delta
77 changes: 53 additions & 24 deletions gnss_tec/glo.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Module contains utils to extract GLONASS frequency numbers from navigation
files."""
from collections import defaultdict
from datetime import datetime

from gnss_tec.nav import nav

Expand All @@ -12,24 +13,39 @@


class NavigationFileError(Exception):
"""Exception raised for errors in navigation file processing"""
pass


class FetchSlotFreqNumError(Exception):
"""Exception raised when we can't find frequency number of the slot"""
pass


def _is_string_like(obj):
"""Check whether obj behaves like a string."""
def _is_string_like(obj: object) -> bool:
"""
Check whether obj behaves like a string.

Parameters
----------
obj : object
Object to check

Returns
-------
bool
True if the object behaves like a string, False otherwise
"""
try:
obj + ''
return True
except (TypeError, ValueError):
return False
return True


def collect_freq_nums(file):
"""Collect GLONASS frequency numbers from a navigation file.
def collect_freq_nums(file) -> dict:
"""
Collect GLONASS frequency numbers from a navigation file.

Parameters
----------
Expand All @@ -49,35 +65,39 @@ def collect_freq_nums(file):
"""
freq_num_timestamps = defaultdict(dict)

f_own = False
# Open file if string was provided, otherwise use the file-like object
should_close_file = False
if _is_string_like(file):
f_own = True
should_close_file = True
file_handler = open(file)
else:
file_handler = file

for slot, epoch, f_num in nav(file_handler):
if f_num in freq_num_timestamps[slot]:
continue
freq_num_timestamps[slot][f_num] = epoch

if f_own:
file_handler.close()

try:
# Process navigation dara
for slot, epoch, freq_num in nav(file_handler):
if freq_num in freq_num_timestamps[slot]:
continue
freq_num_timestamps[slot][freq_num] = epoch
finally:
# Ensure file is closed of we opened it
if should_close_file:
file_handler.close()

# Mapping timestamps to frequency numbers
frequency_numbers = defaultdict(dict)
for slot in freq_num_timestamps:
f_nums = list(freq_num_timestamps[slot].keys())
t_stamps = list(freq_num_timestamps[slot].values())
frequency_numbers[slot] = dict(zip(t_stamps, f_nums))
freq_nums = list(freq_num_timestamps[slot].keys())
time_stamps = list(freq_num_timestamps[slot].values())
frequency_numbers[slot] = dict(zip(time_stamps, freq_nums))

del freq_num_timestamps

# frequency_numbers[<unknown_key>] produces {} so
# we convert defaultdict to dict to avoid possible errors
# Convert defaultdict to dict to avoid possible errors
return dict(frequency_numbers)


def fetch_slot_freq_num(timestamp, slot, freq_nums):
def fetch_slot_freq_num(timestamp: datetime, slot: int, freq_nums: dict) -> float:
"""Find GLONASS frequency number in glo_freq_nums and return it.

Parameters
Expand All @@ -90,34 +110,43 @@ def fetch_slot_freq_num(timestamp, slot, freq_nums):

Returns
-------
freq_num : int
freq_num : float

Raises
------
FetchSlotFreqNumError in case we can't find frequency number of the slot.
"""
freq_num = None

# Attempt to retrieve frequency numbers for the specified slot
try:
slot_freq_nums = freq_nums[slot]
except KeyError:
msg = "Can't find slot {} in the glo_freq_nums dict.".format(slot)
# If the slot is not found in the dictionary, raise an error with a clear message
msg = f"Can't find slot {slot} in the glo_freq_nums dict."
raise FetchSlotFreqNumError(msg)

# Sort the timestamps associated with the slot frequency numbers
dates_times = sorted(slot_freq_nums.keys())

# Loop through sorted timestamps to find the first valid frequency number for the same day
for ts in dates_times:
if timestamp >= ts and timestamp.date() == ts.date():
freq_num = slot_freq_nums[ts]

# If a frequency number is found, return it
if freq_num is not None:
return freq_num

# If no frequency number is found, check if the timestamp matches the first available date
timestamp_date = timestamp.date()
first_date = dates_times[0].date()

# If the timestamp is on the same day as the first available data, return that frequency number
if timestamp_date == first_date:
freq_num = slot_freq_nums[dates_times[0]]
return freq_num
else:
msg = "Can't find GLONASS frequency number for {}.".format(slot)
# If no suitable frequency number is found, raise an error
msg = f"Can't find GLONASS frequency number for {slot}."
raise FetchSlotFreqNumError(msg)
54 changes: 30 additions & 24 deletions gnss_tec/gnss.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,70 +3,76 @@
GNSS codes and frequencies.
"""
from types import MappingProxyType
from enum import Enum

GPS = 'G'
GLO = 'R'
GAL = 'E'
QZSS = 'J'
BDS = 'C'
SBAS = 'S'
IRNSS = 'I'
class GNSS(Enum):
"""Enum to represent different GNSS systems"""
GPS = 'G'
GLO = 'R'
GAL = 'E'
QZSS = 'J'
BDS = 'C'
SBAS = 'S'
IRNSS = 'I'


class GNSSExtra:
"""Special GNSS codes"""
NNSS = 'T'
MIX = 'M'

NNSS = 'T'
MIX = 'M'

# Hz
FREQUENCY = MappingProxyType({
GPS: {
GNSS.GPS: {
1: 1575.42e+06,
2: 1227.60e+06,
5: 1176.45e+06,
},

GLO: {
GNSS.GLO: {
1: lambda k: 1602e+06 + k * 562.5e+03,
2: lambda k: 1246e+06 + k * 437.5e+03,
3: 1202.025e+06,
},

GAL: {
GNSS.GAL: {
1: 1575.420e+06,
5: 1176.450e+06,
7: 1207.140e+06,
8: 1191.795e+06,
6: 1278.750e+06,
},

SBAS: {
GNSS.SBAS: {
1: 1575.42e+06,
5: 1176.45e+06,
},

QZSS: {
GNSS.QZSS: {
1: 1575.42e+06,
2: 1227.60e+06,
5: 1176.45e+06,
6: 1278.75e+06,
},

BDS: {
GNSS.BDS: {
2: 1561.098e+06,
7: 1207.14e+06,
6: 1268.52e+06,
},

IRNSS: {
GNSS.IRNSS: {
5: 1176.45e+06,
9: 2492.028e+06,
},
})

BAND_PRIORITY = MappingProxyType({
GPS: ((1, 2), (1, 5)),
GLO: ((1, 2), (1, 3)),
GAL: ((1, 5), (1, 7), (1, 8), (1, 6)),
SBAS: ((1, 5),),
QZSS: ((1, 2), (1, 5), (1, 6)),
BDS: ((2, 7), (2, 6)),
IRNSS: ((5, 9),),
GNSS.GPS: ((1, 2), (1, 5)),
GNSS.GLO: ((1, 2), (1, 3)),
GNSS.GAL: ((1, 5), (1, 7), (1, 8), (1, 6)),
GNSS.SBAS: ((1, 5),),
GNSS.QZSS: ((1, 2), (1, 5), (1, 6)),
GNSS.BDS: ((2, 7), (2, 6)),
GNSS.IRNSS: ((5, 9),),
})
Loading