Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

GNSS Observations Tutorial

In this tutorial we will explore EarthScope’s new API for fetching GNSS observations (obs) and how the SDK helps facilitate easy (and fast) access:

  • no more parsing RINEX

  • only download what you want

  • easy interopability with common data science libraries via Apache Arrow

Use the client to retrieve GNSS observations

Use es.data.gnss_observations(...) to fetch GNSS observations from api.earthscope.org.

While the endpoint in api.earthscope.org has restrictions (e.g. only one session at a time, 48hr max time range, etc.), this python method transparently fans out requests to satisfy your entire query.

The results come back as a single PyArrow Table.

Convert Arrow to DataFrame

Choose your favorite dataframe library to interact with the results.

In this tutorial, we use Polars. Polars allows us to work with the returned Arrow table in a zero-copy (very efficient!) manner.

In the resulting dataframe, we can see all of the columns returned by default. These are the same data you’re used to working with in RINEX.

Loading...

Slicing and Dicing

This new API facilitates efficiently selecting subsets (slicing) of EarthScope’s dataset so that only the data you want is returned.

You may have noticed in the query above that we were not restricted to 1 day “chunks” like RINEX files. Instead, we asked for an arbitrary 30 hours of data.

In the following example, we’ll look at 2 months of just SNR for only one obs code from one satellite. Only the requested data will be downloaded. This is significantly more efficient than downloading RINEX which contains all fields for all systems, for all satellites, for all observations.

Note that in the resulting dataframe we only have snr - no phase, range, etc. (but of course we can request those fields too if we want!)

Loading...

Visualization

Now let’s make a plot of the mean SNR in 5 minute time bins for the entire result set.

Loading...

Efficient Visualization

Polars works with Altair for visualization. Altair is much faster when using the Rust library vegafusion as a backend.

Plotting satellite arcs

The Polars dataframe has a useful .plot attribute for creating different types of plots. Below, we create a scatter plot using .plot.point(...)

Loading...

Putting it all together

Now let’s grab a some data across multiple stations for multiple satellites and codes.

Loading...
Loading...

Geometry Free

The Geometry-free combination cancels the geometric part of the measurement, leaving all the frequency-dependent effects (i.e., ionospheric refraction, instrumental delays, wind-up) besides multipath and measurement noise. It can be used to estimate the ionospheric electron content, to detect cycle-slips in the carrier phase, or also to estimate antenna rotations as well.

More info at Navipedia

Loading...
Loading...
Loading...

Larger than memory requests

Sometimes the amount of data you want to work with is larger than what fits in your machine’s memory. For such a scenario, you can use the query plan to easily iterate over subsets of the overall result set.

In the following example, I want to process an entire network one day at a time.

Request groups effectively limit the concurrency of requests made to api.earthscope.org as well as limit how much data is loaded into memory at once.

In the following example, we create request groups for each day. All requests in a single group are executed in parallel and collected into a single Table. You can then do whatever you want with that table before proceeding to the next iteration of the loop where another request group is used to create the next table.

1425616 2025-07-20 00:00:00+00:00 2025-07-20 23:59:45+00:00 ['ATW200USA', 'CGNN00USA', 'CLGO00USA', 'GRNX00USA', 'SELD00USA']
1423432 2025-07-21 00:00:00+00:00 2025-07-21 23:59:45+00:00 ['ATW200USA', 'CGNN00USA', 'CLGO00USA', 'GRNX00USA', 'SELD00USA']
1426148 2025-07-22 00:00:00+00:00 2025-07-22 23:59:45+00:00 ['ATW200USA', 'CGNN00USA', 'CLGO00USA', 'GRNX00USA', 'SELD00USA']
1425577 2025-07-23 00:00:00+00:00 2025-07-23 23:59:45+00:00 ['ATW200USA', 'CGNN00USA', 'CLGO00USA', 'GRNX00USA', 'SELD00USA']
1425262 2025-07-24 00:00:00+00:00 2025-07-24 23:59:45+00:00 ['ATW200USA', 'CGNN00USA', 'CLGO00USA', 'GRNX00USA', 'SELD00USA']
1424973 2025-07-25 00:00:00+00:00 2025-07-25 23:59:45+00:00 ['ATW200USA', 'CGNN00USA', 'CLGO00USA', 'GRNX00USA', 'SELD00USA']
1423799 2025-07-26 00:00:00+00:00 2025-07-26 23:59:45+00:00 ['ATW200USA', 'CGNN00USA', 'CLGO00USA', 'GRNX00USA', 'SELD00USA']

We can just as easily process the result set transposed: the entire time window but one station at a time:

2097697 2025-07-20 00:00:00+00:00 2025-07-26 23:59:45+00:00 ['ATW200USA']
1903112 2025-07-20 00:00:00+00:00 2025-07-26 23:59:45+00:00 ['CGNN00USA']
1979093 2025-07-20 00:00:00+00:00 2025-07-26 23:59:45+00:00 ['CLGO00USA']
2195651 2025-07-20 00:00:00+00:00 2025-07-26 23:59:45+00:00 ['GRNX00USA']
1799254 2025-07-20 00:00:00+00:00 2025-07-26 23:59:45+00:00 ['SELD00USA']