From 264540739ef2b6d4b897410b500dde8b694e81b9 Mon Sep 17 00:00:00 2001 From: Eshanta Mishra Date: Wed, 12 Aug 2026 19:30:14 +0000 Subject: [PATCH] Add MVP1 notebook 1 for geodesy --- .../NB1-access-gnss-via-SDK.ipynb | 657 ++++++++++++++++++ 1 file changed, 657 insertions(+) create mode 100644 tutorials/mvp-1-geodesy/NB1-access-gnss-via-SDK.ipynb diff --git a/tutorials/mvp-1-geodesy/NB1-access-gnss-via-SDK.ipynb b/tutorials/mvp-1-geodesy/NB1-access-gnss-via-SDK.ipynb new file mode 100644 index 0000000..0a5d4b3 --- /dev/null +++ b/tutorials/mvp-1-geodesy/NB1-access-gnss-via-SDK.ipynb @@ -0,0 +1,657 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "2abe5d1d-4a2f-4c96-a78c-6c3e5f9ac750", + "metadata": {}, + "source": [ + "# Accessing GNSS Observations with the EarthScope SDK" + ] + }, + { + "cell_type": "markdown", + "id": "35320cb4-aed2-4420-8f9c-b3deb365e792", + "metadata": {}, + "source": [ + "**Version:** 1.0 | **Last updated:** 2026-07-09 \n", + "\n", + "**Author:** Eshanta Mishra | **Author institution:** EarthScope Consortium\n", + "\n", + "**Maintainer:** EarthScope OnRamp Team | **Maintainer's contact :** help@earthscope.org\n", + "\n", + "**Estimated Time:** ~ 30 minutes| **Pathway:** MVP1\n", + "\n", + "**License:** CC-BY-4.0" + ] + }, + { + "cell_type": "markdown", + "id": "b9b1d1c3-569f-459a-8595-e1ba758e4bc7", + "metadata": {}, + "source": [ + "## Introduction\n", + "\n", + "**What this notebook does:** It instantiates the EarthScope SDK and allows you to retrieve GNSS observations for your station of interest.\n", + "\n", + "**Why it is useful:** This notebook provides a hands-on on how to use the SDK to retrieve GNSS observations from the cloud and load it into a dataframe. It provides the users an insight into how modern workflows can be more efficient compared to traditional methods of downloading and accessing GNSS observations as RINEX files.\n", + "\n", + "**What you will accomplish:** By the end of this notebook, we will have accomplished:\n", + "* instantiating the EarthScope SDK client\n", + "* Select data for stations using the station name and time range.\n", + "* Retrieve observations and load into dataframes.\n", + "* Filter (slice) the GNSS data based on different parameters.\n", + "\n", + "---\n", + "\n", + "### Prerequisites\n", + "\n", + "\n", + "Before starting this notebook, you should:\n", + "* [ ] This is an introductory notebook. Being familiar with **Geodesy, GNSS, python dataframes** is recommended but not required.\n", + "\n", + "---\n", + "\n", + "### GeoLab Compute Resources\n", + "\n", + "| Setting | Recommended |\n", + "|---|---|\n", + "| **Image** | GeoLab (default image)|\n", + "| **Server size** | 4 GB RAM, ~0.5 CPUs (default server) |" + ] + }, + { + "cell_type": "markdown", + "id": "811fc68e-1e81-482c-82dd-5efa2a89d715", + "metadata": {}, + "source": [ + "## Learning Objectives\n", + "\n", + "By the end of this notebook, you will be able to:\n", + "\n", + "1. Understand the basics of GNSS data available through the EarthScope SDK.\n", + "2. Use the EarthScope SDK client to retreive GNSS data from EarthScope." + ] + }, + { + "cell_type": "markdown", + "id": "1ec3d79a-ad70-4c58-b8fc-287501adb7e9", + "metadata": {}, + "source": [ + "## Relevant Documentation & Resources\n", + "\n", + "* [EarthScope SDK documentation](https://docs.earthscope.org/sdk)" + ] + }, + { + "cell_type": "markdown", + "id": "076d400d-8c88-4113-a28d-91dac8648679", + "metadata": {}, + "source": [ + "## Contents\n", + "\n", + "1. [Basics of GNSS](#id-1-basics-of-gnss)\n", + "2. [Setup & Imports](#id-2-setup-imports)\n", + "3. [Retrieve Observations for a Single Station](#id-3-retrieve-observations-for-a-single-station)\n", + "4. [Understanding the Observation fields](#id-4-understanding-the-observation-fields)\n", + "5. [Requesting Only the Data you Need](#id-5-requesting-only-the-data-you-need)\n", + "6. [Larger than Memory Requests with Query Plans](#id-6-larger-than-memory-requests-with-query-plans)\n", + "7. [Exploration Exercises](#id-7-exploration-exercises)\n", + "8. [Troubleshooting & Support](#id-8-troubleshooting-support)" + ] + }, + { + "cell_type": "markdown", + "id": "1b245a5d-5792-461d-bd51-99e55ab797a0", + "metadata": {}, + "source": [ + "## 1. Basics of GNSS" + ] + }, + { + "cell_type": "markdown", + "id": "9e03f7ee-00b4-4501-bd77-551d49ca52a6", + "metadata": {}, + "source": [ + "A Global Navigation Satellite System (GNSS) is a constellation of satellites that broadcast timing signals. GNSS includes constellations such as GPS (United States), GLONASS (Russia), BeiDou (China) etc. A ground station (receiver plus antenna) records the data transmitted by these satellites several times per minute.\n", + "\n", + "Each of those recordings is a GNSS observation. It is a raw measurement of the signal at a given instant for a given satellite, on one frequency. Some commonly used observations are:\n", + "\n", + "1. **Pseudorange**: Apparent distance to the satellite, in meters. It is called *pseudo* because the receiver and satellite clocks aren't perfectly synchronized.\n", + "2. **Carrier phase**: It is a more precise distance measurement between the satellite and the ground, counted in whole cycles of the carrier wave (with an unknown starting offset)\n", + "3. **SNR**: Signal-to-Noise Ratio. It is a parameter that tells how strong and clean the received signal is.\n", + "\n", + "**Raw Observations vs. derived products**\n", + "\n", + "The data we retrieve in this notebook are raw observations. The geodetic results you may ultimately want such as a station's position, displacement over time, etc., are derived products. These derived products are computed by processing many observations together. " + ] + }, + { + "cell_type": "markdown", + "id": "cca79b91-0348-4ee2-842e-740595288820", + "metadata": {}, + "source": [ + "## 2. Setup & Imports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8cd2903c-1b18-4877-a421-f19374f8fcf3", + "metadata": {}, + "outputs": [], + "source": [ + "import datetime as dt\n", + "import polars as pl\n", + "import os\n", + "\n", + "from earthscope_sdk import AsyncEarthScopeClient" + ] + }, + { + "cell_type": "markdown", + "id": "50263265-10c7-44c7-a302-37b658b899c8", + "metadata": {}, + "source": [ + "### Instantiating the EarthScope SDK" + ] + }, + { + "cell_type": "markdown", + "id": "8683b5f9-3ffc-4eb4-a8ec-82c61fa196e7", + "metadata": {}, + "source": [ + "#### Authentication" + ] + }, + { + "cell_type": "markdown", + "id": "52c5e185-8dca-42ff-8a9d-89c23297a761", + "metadata": {}, + "source": [ + "Since you use your EarthScope account to log into GeoLab, your EarthScope credentials are already available inside it. The client finds your credentials automatically. This removes the step of logging in again using the CLI or passing tokens manually, as you would do in a non-GeoLab Environment. If you are running this notebook outside geolab, you will need to authenticate using the EarthScope CLI (detailed instructions for doing this can be found [here](https://gitlab.com/earthscope/public/earthscope-cli))." + ] + }, + { + "cell_type": "markdown", + "id": "b0143b85-f435-4ce2-9359-6b5f2b8d3674", + "metadata": {}, + "source": [ + "#### Async client" + ] + }, + { + "cell_type": "markdown", + "id": "421226b8-42bc-4d91-a517-469c29fa6e85", + "metadata": {}, + "source": [ + "`AsyncEarthScopeClient` is the asynchronous client. [Async](https://docs.earthscope.org/sdk/usage#async-usage) lets the SDK process a large query into many concurrent sub-requests, making large quantity of data retrieval more efficient. The SDK also comes with a synchronous `EarthScopeClient` if you prefer. The methods used below are identical on the synchronous client, just remove the `async` / `await` keywords. See [Sync-vs-Async programming](https://www.geeksforgeeks.org/javascript/synchronous-and-asynchronous-programming/) for more information on sync and async programming principles." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "abe833e0-9ca9-48bd-8bfc-749579592f92", + "metadata": {}, + "outputs": [], + "source": [ + "es = AsyncEarthScopeClient()" + ] + }, + { + "cell_type": "markdown", + "id": "aa3e8c77-ceaf-4f6e-be2e-b99b0b7f0125", + "metadata": {}, + "source": [ + "### Configuration\n", + "\n", + "Set your parameters here before running the rest of the notebook. Every subsequent cells read from these variables. So, this is the only place you need to edit to point the notebook at different data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15ecb388-d718-4949-a072-5db6cd398d65", + "metadata": {}, + "outputs": [], + "source": [ + "# Modify these values before running the notebook.\n", + "\n", + "STATION = \"AC6000USA\" # GNSS station (9-character ID)\n", + "SESSION = \"A\" # Session name\n", + "START = dt.datetime(2025, 7, 20, 21) # Query start (UTC)\n", + "END = dt.datetime(2025, 7, 21, 3) # Query end (UTC)\n", + "\n", + "OUTPUT_DIR = os.path.join(os.environ[\"SCRATCH_BUCKET\"], \"mvp1-geodesy-nb1-output\") # output folder on personal scratch space on S3\n", + "\n", + "print(f\"Configuration set. Outputs will be saved to: {OUTPUT_DIR}\")" + ] + }, + { + "cell_type": "markdown", + "id": "f2e9e575-a7b1-4dd8-86a9-17f0fd942f15", + "metadata": {}, + "source": [ + "## 3. Retrieve Observations for a single station" + ] + }, + { + "cell_type": "markdown", + "id": "af77148f-64a0-424a-9dc2-2ec34a8f96e8", + "metadata": {}, + "source": [ + "In this step, we will retrieve GNSS observations for a single station and time window as an Apache Arrow table through the [EarthScope API](https://api.earthscope.org/beta/docs#get-/data-products/gnss/observations).\n", + "\n", + "[Arrow](https://arrow.apache.org/) is a fast, columnar, in-memory format that most dataframe libraries read with little or no copying. The API also lets you request an arbitrary time window, unlike the daily data chunks of RINEX files (RINEX provides one file per station per UTC day).\n", + "\n", + "The expected output is a single table with one row per satellite, per signal, per epoch" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4eb07e18-b72b-4ed4-99b7-558d935fee31", + "metadata": {}, + "outputs": [], + "source": [ + "#describe the request, then .fetch() to run the query.\n", + "table = await es.data.gnss_observations(\n", + " start_datetime=START,\n", + " end_datetime=END,\n", + " station_name=STATION,\n", + " session_name=SESSION,\n", + ").fetch()\n", + "\n", + "table" + ] + }, + { + "cell_type": "markdown", + "id": "b5d3fab2-0ab0-4231-9681-b6e56a117f9d", + "metadata": {}, + "source": [ + "Convert the Arrow table to a [Polars](https://docs.pola.rs/api/python/stable/reference/dataframe/index.html) dataframe with `pl.from_arrow(...)`. This is zero-copy, so it is very efficient. Sorting by `timestamp` makes the rows read chronologically." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82b54655-559f-4b95-8479-a386affe5b91", + "metadata": {}, + "outputs": [], + "source": [ + "df = pl.from_arrow(table).sort(\"timestamp\")\n", + "df" + ] + }, + { + "cell_type": "markdown", + "id": "05678a5b-388b-4d44-8e04-28a8912af92e", + "metadata": {}, + "source": [ + "## 4. Understanding the Observation fields" + ] + }, + { + "cell_type": "markdown", + "id": "6f775aef-3b48-4a78-9d4f-b43b7cd1c143", + "metadata": {}, + "source": [ + "The default data query returns every field - the same information you would find in a RINEX observation file. Let us take a moment to understand the data structure.\n", + "Each row is a single measurement (one satellite, one signal, one instant). Inspect the columns, the constellations present, and the range of signal strengths." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1ab79788-7803-4711-aa4f-f977b8e83e7a", + "metadata": {}, + "outputs": [], + "source": [ + "# Inspect structure and coverage\n", + "print(df.schema) \n", + "print(\"Constellations:\", df[\"system\"].unique().sort().to_list())\n", + "print(\"Observation codes:\", df[\"obs_code\"].unique().sort().to_list())" + ] + }, + { + "cell_type": "markdown", + "id": "c59fb35b-ec60-4879-8a0e-1c6bbe9e4a4a", + "metadata": {}, + "source": [ + "### What each field means\n", + "\n", + "| Column | Type | Meaning |\n", + "|---|---|---|\n", + "| `timestamp` | datetime (UTC) | The epoch of the observation. |\n", + "| `satellite` | int | Satellite number within its constellation (its PRN / slot). Combine with `system` for a globally unique ID. |\n", + "| `obs_code` | str | Which signal was measured, e.g. `1C`, `2W`, `2L` (decoded below). |\n", + "| `range` | float | Pseudorange in meters. It is the apparent satellite to receiver distance. |\n", + "| `phase` | float | Carrier phase in cycles. It is used to drive a precise but ambiguous range measurement. |\n", + "| `snr` | float | Signal strength as carrier-to-noise density ($C/N_0$), roughly in dB-Hz. Higher values correspond to stronger signal. |\n", + "| `slip` | int | Cycle-slip indicator; `null` when phase tracking was continuous. |\n", + "| `flags` | int | Per-observation status flags (e.g. loss-of-lock). |\n", + "| `fcn` | int | Frequency channel number — used by GLONASS's FDMA signals; `0` otherwise. |\n", + "| `system` | str | Constellation code (see below). |\n", + "| `igs` | str | The station's IGS long name (e.g. `AC6000USA`). |\n", + "\n", + "### Reading the `system` column\n", + "\n", + "| Code | Constellation |\n", + "|---|---|\n", + "| `G` | GPS (USA) |\n", + "| `R` | GLONASS (Russia) |\n", + "| `E` | Galileo (EU) |\n", + "| `C` | BeiDou (China) |\n", + "| `J` | QZSS (Japan) |\n", + "| `I` | NavIC / IRNSS (India) |\n", + "| `S` | SBAS (augmentation) |\n", + "\n", + "### Reading the `obs_code` column\n", + "\n", + "An observation code names a specific signal as **band + tracking attribute**:\n", + "\n", + "* The **digit** is the frequency band: `1` (L1), `2` (L2), `5` (L5), and so on.\n", + "* The **letter** is the tracking mode or signal component: `C` (C/A or civil code), `W` (semi-codeless Z-tracking of the encrypted P-code), and `L` / `Q` / `X` / `I` (specific modern civil components).\n", + "\n", + "For example, `1C` is the classic GPS L1 C/A signal, `2W` is L2 P(Y) via Z-tracking, `2L` is the L2C signal." + ] + }, + { + "cell_type": "markdown", + "id": "7f1cd227-e581-4790-97a7-176bfc846f2a", + "metadata": {}, + "source": [ + "## 5. Requesting Only the Data You Need" + ] + }, + { + "cell_type": "markdown", + "id": "c0a4e066-7b6b-4161-9260-a4a825637f2a", + "metadata": {}, + "source": [ + "When you are working with RINEX files, the files include everything: all fields, constellations, satellites etc. Many analyses only need a small slice of these data. Passing filters to `gnss_observations()` in the SDK pushes that selection to the server, so only the data that you asked for is transferred.\n", + "\n", + "The payoff is immediate: requesting a single signal from a single satellite over two months returns in a fraction of the time and requires less memory, where full RINEX for the same window would be orders of magnitude larger and mostly discarded. You also skip RINEX parsing entirely because the results arrive as an Arrow table, rather than a set of daily text files you have to decode first.\n", + "\n", + "*A smaller slice also means less data moved across the network, which keeps costs down on EarthScope's side.*\n", + "\n", + "The resulting output is a dataframe containing only the requested stations, constellations, satellites, signals and fields. For example, only `snr` and `range` columns when `field=[\"snr\",\"range\"]`.\n", + "\n", + "Each filter below is optional, accepts a single value or a list, and shrinks the request:\n", + "\n", + "* `station_name`: one name or a list of names\n", + "* `network_name`: request a whole network at once\n", + "* `system`: constellation code(s), e.g. `\"G\"` or `[\"G\", \"R\"]`\n", + "* `satellite`: specific satellite number(s)\n", + "* `obs_code`: specific signal(s), e.g. `[\"1C\", \"2L\"]`\n", + "* `field`: which measurement column(s) to return, e.g. `[\"snr\", \"range\"]`\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "949a6db5-f949-43f9-930c-88344765e60b", + "metadata": {}, + "outputs": [], + "source": [ + "start = dt.datetime(2025, 7, 20)\n", + "end = start + dt.timedelta(days=7)\n", + "\n", + "table = await es.data.gnss_observations(\n", + " start_datetime=start,\n", + " end_datetime=end,\n", + " station_name=[\"P717\", \"P453\", \"P146\", \"P147\", \"P041\"],\n", + " session_name=\"A\",\n", + " system=[\"G\", \"R\"],\n", + " obs_code=[\"1C\", \"2L\"],\n", + " satellite=[\"7\", \"21\", \"28\"],\n", + " field=[\"snr\", \"range\"],\n", + ").fetch()\n", + "\n", + "df_sliced = pl.from_arrow(table).sort(\"timestamp\")\n", + "df_sliced" + ] + }, + { + "cell_type": "markdown", + "id": "7ccb66b6-ca6e-4d4a-876a-da3b40fb0361", + "metadata": {}, + "source": [ + "### Saving your results\n", + "\n", + "Fetching takes time, so it's worth writing results to disk once you have data you'll reuse. In GeoLab, where you write matters:\n", + "\n", + "| Location | Path | Persistence | Use it for |\n", + "|---|---|---|---|\n", + "| **Home** | `/home/jovyan/` | Private, persistent, 50 GB limit | Notebooks, scripts, and results you want to keep while working on your project |\n", + "| **Shared** | `/home/jovyan/shared/` | Read-only | Datasets and starter notebooks placed there by instructors. You can copy files out, but not modify the original |\n", + "| **Scratch** | `/earthscope-scratch/` (exact path in `os.environ[\"SCRATCH_BUCKET\"]`) | Temporary and auto-deletes after ~2 weeks | Large intermediates over the 50 GB home limit |\n", + "\n", + "Use Parquet rather than CSV. It's columnar, compressed, and preserves data types — your `timestamp` comes back as a datetime and `snr` as a float, with no re-parsing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e9c627a6-7f55-46bc-98bf-abd26db7b3dd", + "metadata": {}, + "outputs": [], + "source": [ + "# Step description: Save the sliced dataframe to your Scratch directory.\n", + "out_path = os.path.join(OUTPUT_DIR, \"sliced_observations.parquet\")\n", + "df_sliced.write_parquet(out_path)\n", + "\n", + "print(f\"Wrote {len(df_sliced):,} rows to {out_path}\")" + ] + }, + { + "cell_type": "markdown", + "id": "ac4a4474-22ee-4078-a340-3a4b62e9e6f1", + "metadata": {}, + "source": [ + "## 6. Larger than Memory Requests with Query Plans" + ] + }, + { + "cell_type": "markdown", + "id": "81e989fa-baf0-4396-920e-fece9f620f43", + "metadata": {}, + "source": [ + "### What is a query plan?" + ] + }, + { + "cell_type": "markdown", + "id": "7a61354a-548f-4459-976c-89d94b1d5104", + "metadata": {}, + "source": [ + "**What it does**: Instead of calling `.fetch()` which returns one big table, you build a query plan by leaving `.fetch()` off. A query plan is iterable, i.e. it gives the result set in manageable groups. This allows you to process one group at a time.\n", + "\n", + "**Why it matters**: Some requests such as an entire network for a week will not fit in memory at once. Query plans also limit how many requests hit the API at the same time. Each group's sub-requests run in parallel and are collected into a single table you can process before moving on.\n", + "\n", + "The expected output is a summary for each group (row count, time span, and stations), rather than one huge dataframe." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1772dd83-792b-4666-a107-392d8522b92f", + "metadata": {}, + "outputs": [], + "source": [ + "# Build a query plan (note: no .fetch())\n", + "start = dt.datetime(2025, 7, 20)\n", + "end = start + dt.timedelta(days=7)\n", + "\n", + "plan = es.data.gnss_observations(\n", + " start_datetime=start,\n", + " end_datetime=end,\n", + " network_name=\"PERM:Alaska\",\n", + " session_name=\"A\",\n", + " system=\"G\",\n", + " field=[\"phase\", \"range\", \"snr\"],\n", + ")\n", + "await plan.plan()\n", + "print(plan) # preview the plan's request/group counts before any data is fetched\n", + "\n", + "def summarize(table):\n", + " # Print a quick summary of one group's table.\n", + " d = pl.from_arrow(table)\n", + "\n", + " # Mean SNR per station for this group: which sites have better signals?\n", + " snr_by_station = (\n", + " d.group_by(\"igs\")\n", + " .agg(pl.col(\"snr\").mean().round(1).alias(\"mean_snr\"))\n", + " .sort(\"mean_snr\", descending=True)\n", + " )\n", + "\n", + " # Other things you might compute:\n", + " # - Weak signals that may indicate obstruction or hardware trouble\n", + " # d.filter(pl.col(\"snr\") < 25).group_by(\"igs\").len()\n", + " # - Append each group to disk and process the whole week later\n", + " # # d.write_parquet(f\"{OUTPUT_DIR}/{d['timestamp'].min():%Y%m%d}.parquet\")\n", + " # -------------------------------------------------------------------\n", + " print(f\"{d['timestamp'].min()} → {d['timestamp'].max()}\")\n", + " print(snr_by_station)" + ] + }, + { + "cell_type": "markdown", + "id": "6f59fcbd-b77a-4733-82cc-b064b081bc66", + "metadata": {}, + "source": [ + "Processing the plan one group at a time. Each group is fetched, processed, and released before the next begins, so the full week never sits in memory at once." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46d1af54-622f-4066-9895-cfd595341cdd", + "metadata": {}, + "outputs": [], + "source": [ + "async for table in plan.group_by_day():\n", + " summarize(table)" + ] + }, + { + "cell_type": "markdown", + "id": "02c76245-1e7a-4663-b398-f0472a58eb68", + "metadata": {}, + "source": [ + "Processing the plan one station at a time across the whole window instead." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34742255-bd16-4197-b151-1777b4753a1d", + "metadata": {}, + "outputs": [], + "source": [ + "async for table in plan.group_by_station():\n", + " summarize(table)" + ] + }, + { + "cell_type": "markdown", + "id": "187c7159-2923-4da7-adfd-d2bf3c41249c", + "metadata": {}, + "source": [ + " > **Check**: Each timestamp range and table represents one group." + ] + }, + { + "cell_type": "markdown", + "id": "8e88b724-65ec-4ef7-9e19-fb6bbf8c895b", + "metadata": {}, + "source": [ + "### A rough guide for choosing a strategy" + ] + }, + { + "cell_type": "markdown", + "id": "d0673a80-e5e9-4256-8f3f-a48ba2ddafa2", + "metadata": {}, + "source": [ + "* Less than a week: `.fetch()` the whole results at once.\n", + "* Weeks to months, or many stations: iterate with `group_by_day()` (or `group_by_station()`)\n", + "* Months to years: define custom batches with `plan.group_by()`." + ] + }, + { + "cell_type": "markdown", + "id": "6324d2fb-1711-4e13-b37c-bab35e7a4395", + "metadata": {}, + "source": [ + "For custom grouping, request ordering and performance details (concurrency, rate limiting, and retries), see the [EarthScope SDK documentation](https://docs.earthscope.org/sdk/query-plans#option-4-custom-grouping-advanced). The SDK applies no size limits by default. For very large queries, you can also cap memory or time (more information available in the same documentation)." + ] + }, + { + "cell_type": "markdown", + "id": "c5008800-7811-4fbc-9721-9869da927fc2", + "metadata": {}, + "source": [ + "## 7. Exploration Exercises\n", + "\n", + "Now that you've completed the core workflow, try modifying the parameters below to explore how the results change.\n", + "\n", + "**Try these modifications:**\n", + "\n", + "1. **Change the station:** Set `STATION` in the Configuration section to a different 9-character station ID and re-run Section 3. Does the station report the same constellations?\n", + "\n", + "2. **Isolate one signal:** In Section 5, request a single `obs_code` (e.g. `\"2L\"`) with `field=\"snr\"`. How much smaller is the result?\n", + "\n", + "3. **Save a different result:** You saved `df_sliced` above. Do the same for the full dataframe `df` from Section 3, giving it a different filename. Compare the two file sizes — how much did filtering the request save you?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "edf9b94a-b045-4fcf-be34-f2e2f958f124", + "metadata": {}, + "outputs": [], + "source": [ + "# Exploration cell — use this space to experiment" + ] + }, + { + "cell_type": "markdown", + "id": "1e147f5a-2352-4a16-b28b-3f5c1dbea902", + "metadata": {}, + "source": [ + "## 8. Troubleshooting & Support\n", + "\n", + "### Further Resources\n", + "\n", + "* [EarthScope SDK Documentation](https://docs.earthscope.org/sdk)\n", + "* [Authentication using EarthScope CLI](https://gitlab.com/earthscope/public/earthscope-cli)(For non-GeoLab environments)\n", + "* [SDK GNSS Observation tutorial](https://docs.earthscope.org/sdk/gnss-obs-tutorial)\n", + "* [GeoLab Documentation](https://docs.earthscope.org/geolab)\n", + "* [GeoLab Community Forum](https://earthscope.discourse.group/latest)\n", + "* [Sync-vs-Async programming](https://www.geeksforgeeks.org/javascript/synchronous-and-asynchronous-programming/)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}