Laika is an open-source GNSS processing library. Laika is similar to projects like RTKlib and GPSTK, but in Python and with a strong focus on readability, usability and easy integration with other optimizers. Laika can process raw GNSS observations with data gathered online from various analysis groups to produce data ready for position/velocity estimation. Laika is designed to produce accurate results whilst still being readable and easy to use. Laika is the perfect tool to develop accurate GNSS-only or GNSS-fusion localisation algorithms.
GNSS satellites orbit the earth broadcasting signals that allow the receiver to determine the distance to each satellite. These satellites have known orbits and so their positions are known. This makes determining the receiver's position a basic 3-dimensional trilateration problem. In practice observed distances to each satellite will be measured with some offset that is caused by the receiver's clock error. This offset also needs to be determined, making it a 4-dimensional trilateration problem.
Since this problem is generally overdetermined (more than 4 satellites to solve the 4d problem) there is a variety of methods to compute a position estimate from the measurements. Laika provides a basic weighted least squares solver for experimental purposes. This is far from optimal due to the dynamic nature of the system, this makes a Bayesian estimator like a Kalman filter the preferred estimator.
However, the above description is over-simplified. Getting accurate distance estimates to satellites and the satellite's position from the receiver observations is not trivial. This is what we call processing of the GNSS observables and it is this procedure laika is designed to make easy.
Astrodog is the main component of the laika. It is a python object, and like the soviet space dogs to which it owes its name, an astrodog will do everything to make the life of its owner easier. Which in this case is fetch and process all the necessary data to transform raw GNSS observables into usable distance measurements and satellite positions ready for position estimation.
Astrodog has a get_sat_info function that will provide an accurate position, velocity and clock error for any satellite at any time in history.
Astrodog has a get_delay function that will provide a pseudorange delay correction for any satellite at any time in history for the requested receiver position. This delay correction includes a correction for the tropospheric signal delay, ionospheric signal delay and differential code biases (DCBs) of the transmitting satellite.
This delay can either be estimated with mathematical models or with DGPS station observations, which is more accurate, but slower and only supported in the continental United States.
GNSS processing requires getting data from the internet from various analysis groups such as NASA's CDDIS. AstroDog downloads files from FTP servers from these groups when it needs them. Downloading and parsing all this data can be slow. AstroDog caches all downloaded files locally to avoid re-downloading.
These files are then parsed by AstroDog and kept in memory. Every one of these parsed objects (DCBs, ionospheric models, satellite orbit polynomials, etc.) has a valid location area and/or a valid time window. Within those windows these objects can provide information relevant to GNSS processing.
To confirm the quality of Laika's GNSS processing, we ran laika's processing and a simple Kalman filter (procedure described in examples) on 2000 minutes of driving of a regular commute in San Francisco. The data comes from a "u-blox M8" chip. The fixes computed with laika's processed data are compared to the live navigation fixes given by the u-blox chip. They compared by looking at the standard deviation of all measured altitudes within every 5×5 m² in the dataset. There is no way to compare horizontal accuracy without ground truth, but there is no reason to believe that vertical and horizontal accuracy are not equally correlated for laika computed positions and u-blox's live positions. Data with the antenna on the roof and antenna inside the car are compared separately, since the results are significantly different.
Laika runs in Python 3.8.2, and has only been tested on Ubuntu 20.04. Running in a virtual environment is recommended.
If you do not yet have numpy and scipy installed. Install them with pip. Having accelerated numpy will make laika much faster.
pip install numpy scipy
Then laika can be installed with
python setup.py install
The tests should now pass.
It is no longer possible to download GNSS data from NASA servers without an account.
You can make an account here. Then create a .netrc file in the laika folder with content:
machine urs.earthdata.nasa.gov login your_username password your_password
The notebook examples require some visualisation packages. To install them first you need
sudo apt-get install libfreetype6-dev
and then with pip
pip install -r requirements_examples.txt --user
Then you should be able to run the notebooks. The notebooks can be opened by running jupyter notebook
and then navigating to the desired .ipynb file.
First time user who gets runtime error on trying the Walkthrough jupyter notebook:
RuntimeError Traceback (most recent call last) Cell In[4], line 11 9 # We use RINEX3 PRNs to identify satellites 10 sat_prn = 'G07' ---> 11 sat_pos, sat_vel, sat_clock_err, sat_clock_drift, ephemeris = dog.get_sat_info(sat_prn, time) 12 print("Satellite's position in ECEF (m) : \n", sat_pos, '\n') 13 print("Satellite's velocity in ECEF (m/s) : \n", sat_vel, '\n')
File ~/opt/anaconda3/envs/missile-tid/lib/python3.10/site-packages/laika/astro_dog.py:265, in AstroDog.get_sat_info(self, prn, time) 263 eph = None 264 if self.pull_orbit: --> 265 eph = self.get_orbit(prn, time) 266 if not eph and self.pull_nav: 267 eph = self.get_nav(prn, time)
File ~/opt/anaconda3/envs/missile-tid/lib/python3.10/site-packages/laika/astro_dog.py:106, in AstroDog.get_orbit(self, prn, time) 104 def get_orbit(self, prn: str, time: GPSTime): 105 skip_download = time in self.orbit_fetched_times --> 106 orbit = self._get_latest_valid_data(self.orbits[prn], self.cached_orbit[prn], self.get_orbit_data, time, skip_download) 107 if orbit is not None: 108 self.cached_orbit[prn] = orbit
File ~/opt/anaconda3/envs/missile-tid/lib/python3.10/site-packages/laika/astro_dog.py:363, in AstroDog._get_latest_valid_data(self, data, latest_data, download_data_func, time, skip_download, recv_pos) 361 download_data_func(time, recv_pos) 362 else: --> 363 download_data_func(time) 364 latest_data = get_closest(time, data, recv_pos=recv_pos) 365 if is_valid(latest_data):
File ~/opt/anaconda3/envs/missile-tid/lib/python3.10/site-packages/laika/astro_dog.py:211, in AstroDog.get_orbit_data(self, time, only_predictions) 209 ephems_sp3 = self.download_parse_orbit(time) 210 if sum([len(v) for v in ephems_sp3.values()]) < 5: --> 211 raise RuntimeError(f'No orbit data found. For Time {time.as_datetime()} constellations {self.valid_const} valid ephem types {self.valid_ephem_types}') 213 self.add_orbits(ephems_sp3)
RuntimeError: No orbit data found. For Time 2018-01-07 00:00:00 constellations ['GPS', 'GLONASS'] valid ephem types (
Output of this code block: ```
from datetime import datetime from laika.gps_time import GPSTime time = GPSTime.from_datetime(datetime(2018, 1, 7))
sat_prn = 'G07' sat_pos, sat_vel, sat_clock_err, sat_clock_drift, ephemeris = dog.get_sat_info(sat_prn, time) print("Satellite's position in ECEF (m) : \n", sat_pos, '\n') print("Satellite's velocity in ECEF (m/s) : \n", sat_vel, '\n') print("Satellite's clock error (s) : \n", sat_clock_err, '\n\n')
receiver_position = [-2702584.60036925, -4325039.45362552, 3817393.16034817] delay = dog.get_delay(sat_prn, time, receiver_position) print("Satellite's delay correction (m) in San Fransisco \n", delay) ```
Which prints out:
Downloading https://github.com/commaai/gnss-data-alt/raw/master/MCC/PRODUCTS/18006/final/Sta19826.sp3
Downloading https://github.com/commaai/gnss-data-alt/raw/master/MCC/PRODUCTS/18008/final/Sta19831.sp3
Downloading https://github.com/commaai/gnss-data-alt/raw/master/MCC/PRODUCTS/18007/final/Sta19830.sp3
Downloading https://github.com/commaai/gnss-data/raw/master/gnss/products/1982/igs19826.sp3.Z
Downloading https://github.com/commaai/gnss-data/raw/master/gnss/products/1983/igs19830.sp3.Z
Downloading https://github.com/commaai/gnss-data/raw/master/gnss/products/1983/igs19831.sp3.Z
Downloading https://github.com/commaai/gnss-data/raw/master/gnss/products/1982/igr19826.sp3.Z
Downloading https://github.com/commaai/gnss-data/raw/master/gnss/products/1983/igr19831.sp3.Z
Downloading https://github.com/commaai/gnss-data/raw/master/gnss/products/1983/igr19830.sp3.Z
Downloading https://github.com/commaai/gnss-data-alt/raw/master/MCC/PRODUCTS/18007/rapid/Sta19830.sp3
Downloading https://github.com/commaai/gnss-data-alt/raw/master/MCC/PRODUCTS/18008/rapid/Sta19831.sp3
Downloading https://github.com/commaai/gnss-data-alt/raw/master/MCC/PRODUCTS/18006/rapid/Sta19826.sp3
Downloading https://github.com/commaai/gnss-data/raw/master/gnss/products/1983/igu19831_18.sp3.Z
Downloading https://github.com/commaai/gnss-data/raw/master/gnss/products/1982/igu19826_18.sp3.Z
Downloading https://github.com/commaai/gnss-data/raw/master/gnss/products/1983/igu19830_18.sp3.Z
Downloading https://github.com/commaai/gnss-data-alt/raw/master/MCC/PRODUCTS/18006/ultra/Sta19826.sp3
Downloading https://github.com/commaai/gnss-data-alt/raw/master/MCC/PRODUCTS/18007/ultra/Sta19830.sp3
Downloading https://github.com/commaai/gnss-data-alt/raw/master/MCC/PRODUCTS/18008/ultra/Sta19831.sp3
Downloading https://github.com/commaai/gnss-data/raw/master/gnss/products/1983/igu19831_12.sp3.Z
Downloading https://github.com/commaai/gnss-data/raw/master/gnss/products/1982/igu19826_12.sp3.Z
Downloading https://github.com/commaai/gnss-data/raw/master/gnss/products/1983/igu19830_12.sp3.Z
Downloading https://github.com/commaai/gnss-data/raw/master/gnss/products/1983/igu19831_06.sp3.Z
Downloading https://github.com/commaai/gnss-data/raw/master/gnss/products/1983/igu19831_00.sp3.Z
Downloading https://github.com/commaai/gnss-data/raw/master/gnss/products/1982/igu19826_06.sp3.Z
Downloading https://github.com/commaai/gnss-data/raw/master/gnss/products/1983/igu19830_06.sp3.Z
Downloading https://github.com/commaai/gnss-data/raw/master/gnss/products/1983/igu19830_00.sp3.Z
Downloading https://github.com/commaai/gnss-data/raw/master/gnss/products/1982/igu19826_00.sp3.Z
I tried signing up for an Earthdata account and installing laika
from source with a .netrc
file in the root folder with python setup.py install
. Also tried wiping the cache, which is at /tmp/gnss
. Cache looks like this:
``` ls /tmp/gnss cddis_products russian_products
ls /tmp/gnss/cddis_products 1982 1983
ls /tmp/gnss/cddis_products/1982 igr19826.sp3.attempt_time igs19826.sp3.attempt_time igu19826_00.sp3.attempt_time igu19826_06.sp3.attempt_time igu19826_12.sp3.attempt_time igu19826_18.sp3.attempt_time ```
Running on a Mac with python 3.10.
Ultra Rapid Orbits filenames changed to more descriptive names Long_Product_Filenames_v1.0.pdf
This is a pretty simple issue at first glance.
You have a missing dependency in setup.py
for the library atomicwrites
. The actual library calls are in downloader.py
, for example: https://github.com/commaai/laika/blob/5eb0c3c2596dd12a232b83bdb057a716810e89cf/laika/downloader.py#L283
This means that any project that uses laika as a dependency itself will fail unless it explicitly knows to install atomicwrites
itself. But on the project homepage, the author of that package recommends its deprecation:
I thought it'd be a good time to deprecate this package. Python 3 has os.replace and os.rename which probably do well enough of a job for most usecases.
So, what's the need for that atomic_write
function? Could it not be simply replaced by a basic wrapper using native library calls?
Hi,
First of all thank you for the awesome repo. I would like to help solving the following issue
Simple code to reproduce ``` import laika from laika import AstroDog from laika.lib.coordinates import geodetic2ecef from laika.gps_time import GPSTime import datetime
constellations = ['GPS', 'GLONASS', 'GALILEO'] dog = AstroDog(valid_const=constellations, dgps=True) prn ="G07" pos_ecef = geodetic2ecef([46, 6, 123]) gps_time = GPSTime.from_datetime(datetime.datetime(2022, 11, 25, 9, 43, 6))
delay = dog.get_delay(prn, gps_time, pos_ecef)
print(delay) ```
Result :
Downloading https://github.com/commaai/gnss-data-alt/raw/master/MCC/PRODUCTS/22328/rapid/Sta22374.sp3
Downloading https://github.com/commaai/gnss-data-alt/raw/master/MCC/PRODUCTS/22329/rapid/Sta22375.sp3
...
pulling from https://geodesy.noaa.gov/corsdata/coord/coord_14/ to /tmp/gnss/cors_coord/zsu4_14.coord.txt
pulling from https://geodesy.noaa.gov/corsdata/coord/coord_14/ to /tmp/gnss/cors_coord/ztl4_14.coord.txt
...
File "/home/jonathan/Desktop/cloud_locate/laika/laika/dgps.py", line 31, in download_and_parse_station_postions
with open(coord_file_path, 'r+') as coord_file:
FileNotFoundError: [Errno 2] No such file or directory: '/tmp/gnss/cors_coord/ab27_14.coord.txt'
(Let's download it manually : cd /tmp/gnss/cors_coord && wget https://geodesy.noaa.gov/corsdata/coord/coord_14/ab49_14.coord.txt
)
Then, relaunching the same code now gives :
Traceback (most recent call last):
File "/tmp/test_laika.py", line 14, in <module>
delay = dog.get_delay(prn, gps_time, pos_ecef)
File "/home/jonathan/Desktop/cloud_locate/laika/laika/astro_dog.py", line 325, in get_delay
return self._get_delay_dgps(prn, rcv_pos, time)
File "/home/jonathan/Desktop/cloud_locate/laika/laika/astro_dog.py", line 340, in _get_delay_dgps
dgps_corrections = self.get_dgps_corrections(time, rcv_pos)
File "/home/jonathan/Desktop/cloud_locate/laika/laika/astro_dog.py", line 124, in get_dgps_corrections
latest_data = self._get_latest_valid_data(self.dgps_delays, self.cached_dgps, self.get_dgps_data, time, recv_pos=recv_pos)
File "/home/jonathan/Desktop/cloud_locate/laika/laika/astro_dog.py", line 361, in _get_latest_valid_data
download_data_func(time, recv_pos)
File "/home/jonathan/Desktop/cloud_locate/laika/laika/astro_dog.py", line 239, in get_dgps_data
station_names = get_closest_station_names(recv_pos, k=8, max_distance=MAX_DGPS_DISTANCE, cache_dir=self.cache_dir)
File "/home/jonathan/Desktop/cloud_locate/laika/laika/dgps.py", line 65, in get_closest_station_names
return np.array(station_ids)[idxs]
IndexError: index 4 is out of bounds for axis 0 with size 4
Any suggestion on why this happens and how to solve it ?
I'm using Laika to obtain satellite positions in the sky at different times, but I was comparing data from Laika with real measurements and for one of the tests, G28 is offline (sensor doesn't show it, and almanac I use with another similar tool to Laika, also don't report it), but Laika does report it. Is there a way to get Laika to report whether the satellite is online or not? I dug through the code a bit and I can look at the healthy attribute from the PolyEphemeris, but that one still seems to be set to True regardless. Not sure if it's a bug or not, just hoping to get some help so I can use Laika to estimate satellite positions.
Thanks in advance!
Will Laika support RTCM correction data? Would be really nice to have, and perhaps not to difficult to add.
gnss gps glonass rtklib hacktoberfest