Skip to content

Commit 6dab1fe

Browse files
Add utility function for extracting point-based time series from Earth Engine + example notebook (#2278)
* Update common.py * Update common.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update mkdocs.yml * Update docs-build.yml * Update docs-build.yml --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent fd28ab4 commit 6dab1fe

File tree

4 files changed

+332
-0
lines changed

4 files changed

+332
-0
lines changed

.github/workflows/docs-build.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ jobs:
2020
with:
2121
fetch-depth: 0
2222

23+
- name: Install GDAL system dependencies
24+
run: sudo apt-get update && sudo apt-get install -y gdal-bin libgdal-dev
25+
2326
- name: Install uv
2427
uses: astral-sh/setup-uv@v6
2528
with:
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "0",
6+
"metadata": {},
7+
"source": [
8+
"<a href=\"https://githubtocolab.com/gee-community/geemap/blob/master/docs/notebooks/152_extract_timeseries_to_points.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open in Colab\"/></a>\n",
9+
"\n",
10+
"Uncomment the following line to install [geemap](https://geemap.org) if needed."
11+
]
12+
},
13+
{
14+
"cell_type": "code",
15+
"execution_count": null,
16+
"id": "1",
17+
"metadata": {},
18+
"outputs": [],
19+
"source": [
20+
"# %pip install -U geemap"
21+
]
22+
},
23+
{
24+
"cell_type": "markdown",
25+
"id": "2",
26+
"metadata": {},
27+
"source": [
28+
"## Import libraries"
29+
]
30+
},
31+
{
32+
"cell_type": "code",
33+
"execution_count": null,
34+
"id": "3",
35+
"metadata": {},
36+
"outputs": [],
37+
"source": [
38+
"import matplotlib.pyplot as plt\n",
39+
"import ee\n",
40+
"import geemap"
41+
]
42+
},
43+
{
44+
"cell_type": "code",
45+
"execution_count": null,
46+
"id": "4",
47+
"metadata": {},
48+
"outputs": [],
49+
"source": [
50+
"Map = geemap.Map()\n",
51+
"Map"
52+
]
53+
},
54+
{
55+
"cell_type": "markdown",
56+
"id": "5",
57+
"metadata": {},
58+
"source": [
59+
"## Specify Latitude, Longitude, & Date"
60+
]
61+
},
62+
{
63+
"cell_type": "code",
64+
"execution_count": null,
65+
"id": "6",
66+
"metadata": {},
67+
"outputs": [],
68+
"source": [
69+
"# Define the latitude and longitude\n",
70+
"lat = 28.60\n",
71+
"lon = 77.22\n",
72+
"\n",
73+
"# Define time range\n",
74+
"start_date = \"2000-01-01\"\n",
75+
"end_date = \"2015-12-31\""
76+
]
77+
},
78+
{
79+
"cell_type": "markdown",
80+
"id": "7",
81+
"metadata": {},
82+
"source": [
83+
"## Extract ERA5-Land Daily Timeseries"
84+
]
85+
},
86+
{
87+
"cell_type": "code",
88+
"execution_count": null,
89+
"id": "8",
90+
"metadata": {},
91+
"outputs": [],
92+
"source": [
93+
"# Initialize image collection\n",
94+
"image_collection = ee.ImageCollection(\"ECMWF/ERA5_LAND/DAILY_AGGR\")\n",
95+
"\n",
96+
"# Define bands of interest (must match available band names exactly)\n",
97+
"band_names = [\"temperature_2m_min\", \"temperature_2m_max\", \"total_precipitation_sum\"]\n",
98+
"\n",
99+
"# Define the scale\n",
100+
"scale = 11132\n",
101+
"\n",
102+
"# Extract time series at specified latitude and longitude\n",
103+
"result = geemap.extract_timeseries_to_point(\n",
104+
" lat=lat,\n",
105+
" lon=lon,\n",
106+
" image_collection=image_collection,\n",
107+
" band_names=band_names,\n",
108+
" start_date=start_date,\n",
109+
" end_date=end_date,\n",
110+
" scale=scale,\n",
111+
")\n",
112+
"\n",
113+
"# Preview results\n",
114+
"print(result.shape)\n",
115+
"result.head()"
116+
]
117+
},
118+
{
119+
"cell_type": "markdown",
120+
"id": "9",
121+
"metadata": {},
122+
"source": [
123+
"## Extract CHIRPS Daily Precipitation Timeseries"
124+
]
125+
},
126+
{
127+
"cell_type": "code",
128+
"execution_count": null,
129+
"id": "10",
130+
"metadata": {},
131+
"outputs": [],
132+
"source": [
133+
"# Initialize image collection\n",
134+
"image_collection = ee.ImageCollection(\"UCSB-CHG/CHIRPS/DAILY\")\n",
135+
"\n",
136+
"# Define the band name\n",
137+
"band_names = [\"precipitation\"]\n",
138+
"\n",
139+
"# Define the scale\n",
140+
"scale = 5566\n",
141+
"\n",
142+
"# Extract time series at specified latitude and longitude\n",
143+
"result = geemap.extract_timeseries_to_point(\n",
144+
" lat=lat,\n",
145+
" lon=lon,\n",
146+
" image_collection=image_collection,\n",
147+
" band_names=band_names,\n",
148+
" start_date=start_date,\n",
149+
" end_date=end_date,\n",
150+
" scale=scale,\n",
151+
")\n",
152+
"\n",
153+
"# Preview results\n",
154+
"print(result.shape)\n",
155+
"result.head()"
156+
]
157+
},
158+
{
159+
"cell_type": "markdown",
160+
"id": "11",
161+
"metadata": {},
162+
"source": [
163+
"## Extract MODIS Terra Vegetation Indices Timeseries"
164+
]
165+
},
166+
{
167+
"cell_type": "code",
168+
"execution_count": null,
169+
"id": "12",
170+
"metadata": {},
171+
"outputs": [],
172+
"source": [
173+
"# Initialize image collection\n",
174+
"image_collection = ee.ImageCollection(\"MODIS/061/MOD13Q1\")\n",
175+
"\n",
176+
"# Define the band names\n",
177+
"band_names = [\"NDVI\", \"EVI\"]\n",
178+
"\n",
179+
"# Define the scale\n",
180+
"scale = 250\n",
181+
"\n",
182+
"# Extract time series at specified latitude and longitude\n",
183+
"result = geemap.extract_timeseries_to_point(\n",
184+
" lat=28.60,\n",
185+
" lon=77.22,\n",
186+
" image_collection=image_collection,\n",
187+
" band_names=band_names,\n",
188+
" start_date=start_date,\n",
189+
" end_date=end_date,\n",
190+
" scale=scale,\n",
191+
")\n",
192+
"\n",
193+
"# Preview results\n",
194+
"print(result.shape)\n",
195+
"result.head()"
196+
]
197+
},
198+
{
199+
"cell_type": "markdown",
200+
"id": "13",
201+
"metadata": {},
202+
"source": [
203+
"## Plot the Timeseries"
204+
]
205+
},
206+
{
207+
"cell_type": "code",
208+
"execution_count": null,
209+
"id": "14",
210+
"metadata": {},
211+
"outputs": [],
212+
"source": [
213+
"# Simple plot\n",
214+
"plt.figure(figsize=(12, 5))\n",
215+
"\n",
216+
"for band in [\"NDVI\", \"EVI\"]:\n",
217+
" plt.plot(\n",
218+
" result[\"time\"], result[band] * 0.0001, label=band\n",
219+
" ) # apply scale factor of 0.0001\n",
220+
"\n",
221+
"plt.title(f\"NDVI & EVI Time Series at {lat, lon}\")\n",
222+
"plt.xlabel(\"Date\")\n",
223+
"plt.ylabel(\"Value\")\n",
224+
"plt.legend()\n",
225+
"plt.tight_layout()\n",
226+
"plt.show()"
227+
]
228+
}
229+
],
230+
"metadata": {
231+
"kernelspec": {
232+
"display_name": "geo",
233+
"language": "python",
234+
"name": "geo"
235+
},
236+
"language_info": {
237+
"codemirror_mode": {
238+
"name": "ipython",
239+
"version": 3
240+
},
241+
"file_extension": ".py",
242+
"mimetype": "text/x-python",
243+
"name": "python",
244+
"nbconvert_exporter": "python",
245+
"pygments_lexer": "ipython3",
246+
"version": "3.12.11"
247+
}
248+
},
249+
"nbformat": 4,
250+
"nbformat_minor": 5
251+
}

geemap/common.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7761,6 +7761,83 @@ def extract_values_to_points(
77617761
return result
77627762

77637763

7764+
def extract_timeseries_to_point(
7765+
lat,
7766+
lon,
7767+
image_collection,
7768+
start_date,
7769+
end_date,
7770+
band_names,
7771+
scale=None,
7772+
crs=None,
7773+
crsTransform=None,
7774+
out_df=None,
7775+
):
7776+
"""
7777+
Extracts pixel time series from an ee.ImageCollection at a point.
7778+
7779+
Args:
7780+
lat (float): Latitude of the point.
7781+
lon (float): Longitude of the point.
7782+
image_collection (ee.ImageCollection): Image collection to sample.
7783+
start_date (str): Start date (e.g., '2020-01-01').
7784+
end_date (str): End date (e.g., '2020-12-31').
7785+
band_names (list): List of bands to extract.
7786+
scale (float): Sampling scale in meters.
7787+
crs (str, optional): Projection CRS. Defaults to image CRS.
7788+
crsTransform (list, optional): CRS transform matrix (3x2 row-major). Overrides scale.
7789+
out_df (str, optional): File path to save CSV. If None, returns a DataFrame.
7790+
7791+
Returns:
7792+
pd.DataFrame or None: Time series data if not exporting to CSV.
7793+
"""
7794+
7795+
import pandas as pd
7796+
from datetime import datetime
7797+
7798+
if not isinstance(image_collection, ee.ImageCollection):
7799+
raise ValueError("image_collection must be an instance of ee.ImageCollection.")
7800+
7801+
property_names = image_collection.first().propertyNames().getInfo()
7802+
if "system:time_start" not in property_names:
7803+
raise ValueError("The image collection lacks the 'system:time_start' property.")
7804+
7805+
point = ee.Geometry.Point([lon, lat])
7806+
7807+
try:
7808+
image_collection = (
7809+
image_collection.filterBounds(point)
7810+
.filterDate(start_date, end_date)
7811+
.select(band_names)
7812+
)
7813+
except Exception as e:
7814+
raise RuntimeError(f"Error filtering image collection: {e}")
7815+
7816+
try:
7817+
result = image_collection.getRegion(
7818+
geometry=point, scale=scale, crs=crs, crsTransform=crsTransform
7819+
).getInfo()
7820+
7821+
result_df = pd.DataFrame(result[1:], columns=result[0])
7822+
7823+
if result_df.empty:
7824+
raise ValueError(
7825+
"Extraction returned an empty DataFrame. Check your point, date range, or selected bands."
7826+
)
7827+
7828+
result_df["time"] = result_df["time"].apply(
7829+
lambda t: datetime.utcfromtimestamp(t / 1000)
7830+
)
7831+
7832+
if out_df:
7833+
result_df.to_csv(out_df, index=False)
7834+
else:
7835+
return result_df
7836+
7837+
except Exception as e:
7838+
raise RuntimeError(f"Error extracting data: {e}.")
7839+
7840+
77647841
def image_reclassify(img, in_list, out_list):
77657842
"""Reclassify an image.
77667843

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ nav:
312312
- notebooks/149_gemini.ipynb
313313
- notebooks/150_maplibre.ipynb
314314
- notebooks/151_dataset_explorer.ipynb
315+
- notebooks/152_extract_timeseries_to_point.ipynb
315316
# - miscellaneous:
316317
# - notebooks/cartoee_colab.ipynb
317318
# - notebooks/cartoee_colorbar.ipynb

0 commit comments

Comments
 (0)