Quickstart#
This library provides functions to hone in on coincident datasets and visualize their coverage.
The current recommended workflow is to:
start with a lidar dataset
reduce to region with coincident maxar stereo within an acceptable temporal range
optionally reduce further with additional datasets such as icesat-2 and gedi altimetry
This notebook provides an example starting from USGS 3DEP LiDAR in Colorado, USA
import geopandas as gpd
import coincident
print(coincident.__version__)
/home/docs/checkouts/readthedocs.org/user_builds/coincident/checkouts/104/src/coincident/io/download.py:27: TqdmExperimentalWarning: Using `tqdm.autonotebook.tqdm` in notebook mode. Use `tqdm.tqdm` instead to force console mode (e.g. in jupyter console)
from tqdm.autonotebook import tqdm
0.5.dev4+gc0bf3bd.d20250804
Define an area of interest#
Searches often start with an spatial subset, in this notebook we know we are interested in datasets in Colorado. We recommend restricting searches to areas at the ‘State’ scale rather than Country or Global scales
aoi = gpd.read_file(
"https://raw.githubusercontent.com/unitedstates/districts/refs/heads/gh-pages/states/CO/shape.geojson"
)
aoi.explore(color="black", style_kwds=dict(fill=False))
Note
this polygon for the state of Colorado is simple and has only 4 vertices in the corners, but it’s good practice good to check the number of vertices you have before searching. The simpler your search polygons are the faster your searches will be!
print("Original number of vertices:", aoi.count_coordinates().iloc[0])
aoi = aoi.simplify(0.01)
print("Simplified number of vertices:", aoi.count_coordinates().iloc[0])
Original number of vertices: 260
Simplified number of vertices: 7
Uniform search method#
the coincident package provides a search() method that has the same syntax regardless of which dataset you are searching. Behind the scenes, polygons intersecting your area of interest are efficiently located and returned as a geodataframe.
For 3DEP LiDAR, we start by searching bounding boxes for each ‘workunit’. Once we identify a workunit, we load a precise polygon delineating the extent of LiDAR measurements.
The search function is essentially a pystac_client.ItemSearch with an extra argument dataset.
coincident.datasets.aliases
['maxar',
'3dep',
'icesat-2',
'gedi',
'gliht',
'ablvis2_1',
'aflvis2_1',
'cop30',
'worldcover',
'tdx',
'noaa',
'ncalm',
'neon']
gf = coincident.search.search(
dataset="3dep",
intersects=aoi,
datetime=["2018", "2024"],
)
gf.explore(column="workunit", popup=True)
# From this search we identify a specific lidar acquisition of interest
gf = gf[gf.workunit == "CO_WestCentral_2019"]
gf
| minx | maxx | miny | maxy | geometry | workunit | workunit_id | project | project_id | start_datetime | ... | onemeter_reason | seamless_category | seamless_reason | lpc_link | sourcedem_link | metadata_link | collection | datetime | dayofyear | duration | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| fid | |||||||||||||||||||||
| 2446 | -107.989311 | -106.135696 | 38.134274 | 39.389549 | POLYGON ((-106.1357 38.13427, -106.1357 39.389... | CO_WestCentral_2019 | 175984 | CO_WestCentral_2019_A19 | 175987 | 2019-08-21 | ... | Meets 3DEP 1-m DEM requirements | Meets | Meets 3DEP seamless DEM requirements | https://rockyweb.usgs.gov/vdelivery/Datasets/S... | http://prd-tnm.s3.amazonaws.com/index.html?pre... | http://prd-tnm.s3.amazonaws.com/index.html?pre... | 3DEP | 2019-09-04 12:00:00 | 247 | 29 |
1 rows × 37 columns
Each Workunit has a unique ‘Feature ID’, or ‘FID’ that can be used to efficiently retrieve the full-resultion detailed MultiPolygon footprint from the USGS’s WESM.gpkg file in AWS
gf.index.values
array([2446])
USGS 3DEP Lidar#
Some datasets have additional functions available to load auxiliary data. For example, we can load original high resolution polygon. In addition to loading “swath” polygons to understand exact days when acquisitions were made.
Warning
Swath polygons are not available for all LiDAR workunits. They are more likely to exist for acquisitions after 2018.
gf_wesm = coincident.search.wesm.load_by_fid(fids=gf.index.values)
gf_wesm.explore(column="workunit", popup=True)
gf_wesm[["workunit", "start_datetime", "end_datetime", "duration"]] # duration in days
| workunit | start_datetime | end_datetime | duration | |
|---|---|---|---|---|
| 0 | CO_WestCentral_2019 | 2019-08-21 | 2019-09-19 | 29 |
Within this ‘workunit’ data was collected over 29 days, in order to see when data was collected within this polygon we need to load a corresponding ‘swath’ polygon. coincident provides a helper function for this, which loads the swath polygon for a given workunit
Note
Swath polygons have detailed timing information for individual LiDAR flight lines composing a given ‘workunit’. Not all workunits have swath polygons. They tend to be available for data collected after 2019.
# NOTE: be patient here, reading shapefiles from s3 can be slow depending on your bandwidth
gf_swath = coincident.search.wesm.get_swath_polygons(gf.workunit.iloc[0])
print("Number of swath polygons:", len(gf_swath))
Number of swath polygons: 314
# Plot them, simplify first for faster plotting
gf_swath["geometry"] = gf_swath.simplify(0.01)
gf_swath.explore(column="dayofyear", cmap="plasma")
As you can see in the above figure, the actual day of observation for any point on the ground can vary in complex ways based on the flight paths taken during the LiDAR collection period.
Maxar stereo search#
Now that we understand our lidar collection, let’s return to the uniform search function, and now search for Maxar stereo pairs. We are only interested in pairs in the same date range of the lidar. Note that searching by a simplified polygon (in this case back to the convex hull we started with) is recommended as most APIs have limits on the number of polygon vertices.
# Simplify search polygon and add date range
pad = gpd.pd.Timedelta(days=14)
date_range = [gf.start_datetime.iloc[0] - pad, gf.end_datetime.iloc[0] + pad]
aoi = gf.geometry
gf_maxar = coincident.search.search(
dataset="maxar",
intersects=aoi,
datetime=date_range,
filter="eo:cloud_cover < 20",
)
print(len(gf_maxar))
gf_maxar.head()
16
| assets | bbox | collection | geometry | id | links | stac_extensions | stac_version | type | acquisition_rev_number | ... | utc_hour | utc_month_day | utc_time_of_day | view:azimuth | view:off_nadir | view:sun_azimuth | view:sun_elevation | view:sun_elevation_max | view:sun_elevation_min | dayofyear | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | {'browse': {'href': 'https://api.maxar.com/dis... | {'xmin': -106.641662, 'ymin': 37.974755, 'xmax... | wv01 | POLYGON ((-106.64098 39.03543, -106.64166 38.9... | 102001008EC5AC00 | [{'href': 'https://api.maxar.com/discovery/v1/... | [https://stac-extensions.github.io/eo/v1.1.0/s... | 1.1.0 | Feature | 66956 | ... | 20 | 09-30 | 20:55:50 | 345.480086 | 28.822183 | 220.865526 | 40.322631 | 40.703890 | 39.929020 | 273 |
| 1 | {'browse': {'href': 'https://api.maxar.com/dis... | {'xmin': -106.792525, 'ymin': 37.981852, 'xmax... | wv01 | POLYGON ((-106.79232 39.02773, -106.79092 38.9... | 102001008BD60800 | [{'href': 'https://api.maxar.com/discovery/v1/... | [https://stac-extensions.github.io/eo/v1.1.0/s... | 1.1.0 | Feature | 66956 | ... | 20 | 09-30 | 20:55:29 | 317.018132 | 18.552592 | 220.605829 | 40.470560 | 40.825860 | 40.058980 | 273 |
| 2 | {'browse': {'href': 'https://api.maxar.com/dis... | {'xmin': -106.634773, 'ymin': 37.982046, 'xmax... | wv01 | POLYGON ((-106.63038 39.01358, -106.63372 38.9... | 102001008BE9BB00 | [{'href': 'https://api.maxar.com/discovery/v1/... | [https://stac-extensions.github.io/eo/v1.1.0/s... | 1.1.0 | Feature | 66956 | ... | 20 | 09-30 | 20:54:59 | 239.622018 | 18.758134 | 220.643398 | 40.449618 | 40.805820 | 40.043854 | 273 |
| 3 | {'browse': {'href': 'https://api.maxar.com/dis... | {'xmin': -106.812163, 'ymin': 37.955262, 'xmax... | wv01 | POLYGON ((-106.80822 39.01663, -106.80613 38.9... | 1020010088168700 | [{'href': 'https://api.maxar.com/discovery/v1/... | [https://stac-extensions.github.io/eo/v1.1.0/s... | 1.1.0 | Feature | 66956 | ... | 20 | 09-30 | 20:54:39 | 219.950260 | 30.843899 | 220.361555 | 40.572175 | 40.947075 | 40.152527 | 273 |
| 5 | {'browse': {'href': 'https://api.maxar.com/dis... | {'xmin': -108.075387, 'ymin': 38.875481, 'xmax... | wv03-vnir | POLYGON ((-108.07422 39.13516, -108.07281 39.1... | 104001005232D700 | [{'href': 'https://api.maxar.com/discovery/v1/... | [https://stac-extensions.github.io/eo/v1.1.0/s... | 1.1.0 | Feature | 27708 | ... | 18 | 09-23 | 18:16:05 | 284.810018 | 11.936680 | 161.254106 | 49.480808 | 49.604393 | 49.310028 | 266 |
5 rows × 69 columns
with gpd.pd.option_context("display.max_rows", None, "display.max_columns", None):
print(gf_maxar.iloc[0])
assets {'browse': {'href': 'https://api.maxar.com/dis...
bbox {'xmin': -106.641662, 'ymin': 37.974755, 'xmax...
collection wv01
geometry POLYGON ((-106.640977 39.035433, -106.641662 3...
id 102001008EC5AC00
links [{'href': 'https://api.maxar.com/discovery/v1/...
stac_extensions [https://stac-extensions.github.io/eo/v1.1.0/s...
stac_version 1.1.0
type Feature
acquisition_rev_number 66956
associations []
collect_time_end 2019-09-30T20:55:58.040916Z
collect_time_start 2019-09-30T20:55:50.706955Z
constellation maxar
datetime 2019-09-30 20:55:50.706955+00:00
eo:bands [{'center_wavelength': 650.0, 'name': 'pan'}]
eo:cloud_cover 0.003715
geolocation_uncertainty 8.339009
gsd 0.627205
instruments [VNIR]
local_hour 13
local_month_day 09-30
local_time_of_day 13:55:50
local_time_of_day_with_timezone 13:55:50-0700
multi_resolution_avg NaN
multi_resolution_end NaN
multi_resolution_max NaN
multi_resolution_min NaN
multi_resolution_start NaN
off_nadir_avg 28.822183
off_nadir_end 26.05217
off_nadir_max 31.65722
off_nadir_min 26.05217
off_nadir_start 31.65722
pan_resolution_avg 0.627205
pan_resolution_end 0.598924
pan_resolution_max 0.658881
pan_resolution_min 0.598924
pan_resolution_start 0.658881
platform worldview-01
scan_direction forward
spacecraft_to_target_azimuth_avg 345.480086
spacecraft_to_target_azimuth_end 344.75037
spacecraft_to_target_azimuth_max 346.21698
spacecraft_to_target_azimuth_min 344.75037
spacecraft_to_target_azimuth_start 346.21698
stereo_pair_identifiers [11a11283-6661-4f91-9bcb-5dab3c6a5d02-inv]
target_to_spacecraft_azimuth_avg 165.480086
target_to_spacecraft_azimuth_end 164.75037
target_to_spacecraft_azimuth_max 166.21698
target_to_spacecraft_azimuth_min 164.75037
target_to_spacecraft_azimuth_start 166.21698
target_to_spacecraft_elevation_avg 58.692128
target_to_spacecraft_elevation_end 61.748469
target_to_spacecraft_elevation_max 61.748469
target_to_spacecraft_elevation_min 55.552526
target_to_spacecraft_elevation_start 55.552526
timezone_offset -7
title Maxar WV01 Image 102001008EC5AC00
utc_hour 20
utc_month_day 09-30
utc_time_of_day 20:55:50
view:azimuth 345.480086
view:off_nadir 28.822183
view:sun_azimuth 220.865526
view:sun_elevation 40.322631
view:sun_elevation_max 40.70389
view:sun_elevation_min 39.92902
dayofyear 273
Name: 0, dtype: object
# Group by stereo pair id (NOTE: assumes single id per mono acquisition)
gf_maxar["stereo_pair_id"] = gf_maxar.stereo_pair_identifiers.str[0]
gf_stereo = gf_maxar.dissolve(by="stereo_pair_id", as_index=False)
gf_stereo.explore(column="stereo_pair_identifiers")
m = gf_wesm.explore(color="black")
gf_maxar.explore(column="dayofyear", cmap="plasma", m=m, popup=True)
Restrict polygon footprints to area of overlap#
We will make regular use of the GeoPandas.overlay() function to shrink original acquisition footprints so that they only overlap an area of interest.
# Consider area of overlap
m = gf_wesm.explore(color="black")
# NOTE: don't really need wesm attributes in result, so just use geodataframe w/ geometry column
# gf_i = gf_wesm[['geometry']].overlay(gf_stereo, how='intersection')
gf_i = gf_stereo.overlay(gf_wesm[["geometry"]], how="intersection")
gf_i.explore(column="dayofyear", cmap="plasma", m=m)
Exclude very small overlaps#
Consider further filtering results by a minimum overlap area criteria, or by a more precise estimate of the number of days elapsed for the maxar stereo acquisition relative to lidar estimated from swath polygons
min_area_km2 = 20
gf_i = coincident.overlaps.subset_by_minimum_area(gf_i, min_area_km2)
# Look at intersection with swath polygons for exact date difference
# Since both have 'dayofyear', these cols get expanded to dayofyear_1 and dayofyear_2
stereo_pair = gf_i.iloc[[0]]
gf_dt = gf_swath.overlay(stereo_pair, how="intersection")
print("Maxar Stereo Acquisition DOY - Swath Lidar DOY:")
print(stereo_pair.stereo_pair_id.values[0])
(gf_dt.dayofyear_2 - gf_dt.dayofyear_1).describe()
Maxar Stereo Acquisition DOY - Swath Lidar DOY:
11a11283-6661-4f91-9bcb-5dab3c6a5d02-inv
count 84.000000
mean 30.702381
std 2.736543
min 27.000000
25% 28.750000
50% 30.000000
75% 34.000000
max 36.000000
dtype: float64
GEDI#
Search for Coincident GEDI L2A.
# We've refined our search polygon again, so use a new AOI
aoi = gpd.GeoSeries(gf_i.union_all().convex_hull, crs="EPSG:4326")
gf_gedi = coincident.search.search(
dataset="gedi",
intersects=aoi,
datetime=date_range,
)
print(len(gf_gedi))
gf_gedi.head()
9
| assets | bbox | collection | geometry | id | links | stac_extensions | stac_version | type | datetime | end_datetime | start_datetime | storage:schemes | dayofyear | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | {'002/GEDI02_A_2019221181154_O03725_02_T03077_... | {'xmin': -130.7824147, 'ymin': 14.5375478, 'xm... | GEDI02_A_002 | POLYGON ((-130.74077 14.53755, -128.39101 17.5... | GEDI02_A_2019221181154_O03725_02_T03077_02_003... | [{'href': 'https://cmr.earthdata.nasa.gov/stac... | [https://stac-extensions.github.io/storage/v2.... | 1.1.0 | Feature | 2019-08-09 18:11:54+00:00 | 2019-08-09 19:44:41+00:00 | 2019-08-09 18:11:54+00:00 | {'aws': {'bucket': 'lp-prod-protected', 'platf... | 221 |
| 1 | {'002/GEDI02_A_2019228215125_O03836_03_T03938_... | {'xmin': -154.5108058, 'ymin': 0.0764856, 'xma... | GEDI02_A_002 | POLYGON ((-154.51081 51.7766, -148.53387 51.60... | GEDI02_A_2019228215125_O03836_03_T03938_02_003... | [{'href': 'https://cmr.earthdata.nasa.gov/stac... | [https://stac-extensions.github.io/storage/v2.... | 1.1.0 | Feature | 2019-08-16 21:51:25+00:00 | 2019-08-16 23:24:15+00:00 | 2019-08-16 21:51:25+00:00 | {'aws': {'bucket': 'lp-prod-protected', 'platf... | 228 |
| 2 | {'002/GEDI02_A_2019232140204_O03893_02_T00583_... | {'xmin': -131.649277, 'ymin': 15.7666963, 'xma... | GEDI02_A_002 | POLYGON ((-131.60759 15.7667, -129.21939 18.73... | GEDI02_A_2019232140204_O03893_02_T00583_02_003... | [{'href': 'https://cmr.earthdata.nasa.gov/stac... | [https://stac-extensions.github.io/storage/v2.... | 1.1.0 | Feature | 2019-08-20 14:02:04+00:00 | 2019-08-20 15:34:53+00:00 | 2019-08-20 14:02:04+00:00 | {'aws': {'bucket': 'lp-prod-protected', 'platf... | 232 |
| 3 | {'002/GEDI02_A_2019247142840_O04126_03_T02362_... | {'xmin': -154.8002265, 'ymin': -0.0163617, 'xm... | GEDI02_A_002 | POLYGON ((-154.80023 51.77634, -148.81565 51.5... | GEDI02_A_2019247142840_O04126_03_T02362_02_003... | [{'href': 'https://cmr.earthdata.nasa.gov/stac... | [https://stac-extensions.github.io/storage/v2.... | 1.1.0 | Feature | 2019-09-04 14:28:40+00:00 | 2019-09-04 16:01:29+00:00 | 2019-09-04 14:28:40+00:00 | {'aws': {'bucket': 'lp-prod-protected', 'platf... | 247 |
| 4 | {'002/GEDI02_A_2019251125026_O04187_03_T01704_... | {'xmin': -153.6270991, 'ymin': 0.0029707, 'xma... | GEDI02_A_002 | POLYGON ((-153.6271 51.77622, -147.64024 51.60... | GEDI02_A_2019251125026_O04187_03_T01704_02_003... | [{'href': 'https://cmr.earthdata.nasa.gov/stac... | [https://stac-extensions.github.io/storage/v2.... | 1.1.0 | Feature | 2019-09-08 12:50:26+00:00 | 2019-09-08 14:23:15+00:00 | 2019-09-08 12:50:26+00:00 | {'aws': {'bucket': 'lp-prod-protected', 'platf... | 251 |
gf_gedi.explore()
Altimeter granules span a large geographic area! So let’s again cut results down to the area of intersection.
# Normalize colormap across both dataframes
vmin, vmax = gpd.pd.concat([gf_i.dayofyear, gf_gedi.dayofyear]).agg(["min", "max"])
cmap = "plasma"
# NOTE: here we just take the boundary of the union of all maxar+lidar regions to avoid many overlay geometries
union = gpd.GeoDataFrame(geometry=[gf_i.union_all()], crs="EPSG:4326")
m = gf_i.explore(column="dayofyear", cmap=cmap, vmin=vmin, vmax=vmax)
gf_gedi = union.overlay(gf_gedi, how="intersection")
gf_gedi.explore(m=m, column="dayofyear", cmap=cmap, vmin=vmin, vmax=vmax, legend=False)
m
Immediately we see some potential for close-in time acquisitions.
Note
As an additional step, you might want to add another filtering step to remove tiny polygons
# For each Stereo pair, describe number of altimeter passes and day offset
stereo_pair = gf_i.iloc[[0]]
gf_dt = stereo_pair.overlay(gf_gedi, how="intersection")
m = stereo_pair.explore(
column="dayofyear", vmin=vmin, vmax=vmax, cmap=cmap, legend=False
)
gf_dt.explore(m=m, column="dayofyear_2", vmin=vmin, vmax=vmax, cmap=cmap)
print("Maxar Stereo Acquisition DOY - GEDI DOY:")
print(stereo_pair.stereo_pair_identifiers.values[0])
(gf_dt.dayofyear_1 - gf_dt.dayofyear_2).describe()
Maxar Stereo Acquisition DOY - GEDI DOY:
['11a11283-6661-4f91-9bcb-5dab3c6a5d02-inv']
count 4.000000
mean 23.000000
std 17.492856
min 3.000000
25% 14.250000
50% 22.000000
75% 30.750000
max 45.000000
dtype: float64
# Consider just the minimum offset for each stereo pair
# NOTE: probably a fancier way to do this using a multiindex/groupby
print("Maxar Stereo Acquisition DOY - GEDI DOY")
for i in range(len(gf_i)):
stereo_pair = gf_i.iloc[[i]]
gf_dt = stereo_pair.overlay(gf_gedi, how="intersection")
min_dt = (gf_dt.dayofyear_1 - gf_dt.dayofyear_2).min()
print(stereo_pair.stereo_pair_identifiers.values[0], min_dt)
Maxar Stereo Acquisition DOY - GEDI DOY
['11a11283-6661-4f91-9bcb-5dab3c6a5d02-inv'] 3
['21d1d4b2-47a4-4c4d-a26f-5c688199b518-inv'] -36
['28b60c45-3164-4cd0-9b43-67dac4e8c7b4-inv'] -31
['41fef87b-0d7c-46c2-a598-bcd178dfb9e0-inv'] -29
['6002024a-ca85-4f3c-8c7f-ce849c175a52-inv'] 3
Note
In this case, we have some altimeter acquisitions that were just 3 days after a maxar stereo acquisition!
ICESat-2#
Search for Coincident ICESat-2 Altimetry (ATL03)
gf_is2 = coincident.search.search(
dataset="icesat-2",
intersects=aoi,
datetime=date_range,
)
print(len(gf_is2))
gf_is2
5
| assets | bbox | collection | geometry | id | links | stac_extensions | stac_version | type | datetime | end_datetime | start_datetime | storage:schemes | dayofyear | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | {'browse': {'href': 'https://data.nsidc.earthd... | {'xmin': -109.90889, 'ymin': 26.94277, 'xmax':... | ATL03_006 | POLYGON ((-109.68811 59.53895, -109.90889 59.5... | ATL03_20190807082015_06150402_006_02.h5 | [{'href': 'https://cmr.earthdata.nasa.gov/clou... | [https://stac-extensions.github.io/storage/v2.... | 1.1.0 | Feature | 2019-08-07 08:20:14.997000+00:00 | 2019-08-07 08:28:45.648000+00:00 | 2019-08-07 08:20:14.997000+00:00 | {'aws': {'bucket': 'nsidc-cumulus-prod-protect... | 219 |
| 1 | {'browse': {'href': 'https://data.nsidc.earthd... | {'xmin': -108.09761, 'ymin': 26.96544, 'xmax':... | ATL03_006 | POLYGON ((-103.41614 59.54107, -103.63704 59.5... | ATL03_20190903191210_10340406_006_02.h5 | [{'href': 'https://cmr.earthdata.nasa.gov/clou... | [https://stac-extensions.github.io/storage/v2.... | 1.1.0 | Feature | 2019-09-03 19:12:09.748000+00:00 | 2019-09-03 19:20:40.343000+00:00 | 2019-09-03 19:12:09.748000+00:00 | {'aws': {'bucket': 'nsidc-cumulus-prod-protect... | 246 |
| 2 | {'browse': {'href': 'https://data.nsidc.earthd... | {'xmin': -109.64978, 'ymin': 26.94274, 'xmax':... | ATL03_006 | POLYGON ((-109.42923 59.53777, -109.64978 59.5... | ATL03_20190905065620_10570402_006_02.h5 | [{'href': 'https://cmr.earthdata.nasa.gov/clou... | [https://stac-extensions.github.io/storage/v2.... | 1.1.0 | Feature | 2019-09-05 06:56:19.240000+00:00 | 2019-09-05 07:04:49.503000+00:00 | 2019-09-05 06:56:19.240000+00:00 | {'aws': {'bucket': 'nsidc-cumulus-prod-protect... | 248 |
| 3 | {'browse': {'href': 'https://data.nsidc.earthd... | {'xmin': -110.29975, 'ymin': 26.95232, 'xmax':... | ATL03_006 | POLYGON ((-110.07845 59.54577, -110.29975 59.5... | ATL03_20190909064801_11180402_006_02.h5 | [{'href': 'https://cmr.earthdata.nasa.gov/clou... | [https://stac-extensions.github.io/storage/v2.... | 1.1.0 | Feature | 2019-09-09 06:48:00.991000+00:00 | 2019-09-09 06:56:31.581000+00:00 | 2019-09-09 06:48:00.991000+00:00 | {'aws': {'bucket': 'nsidc-cumulus-prod-protect... | 252 |
| 4 | {'browse': {'href': 'https://data.nsidc.earthd... | {'xmin': -107.83927, 'ymin': 26.96543, 'xmax':... | ATL03_006 | POLYGON ((-103.15539 59.54093, -103.37634 59.5... | ATL03_20191002174817_00890506_006_02.h5 | [{'href': 'https://cmr.earthdata.nasa.gov/clou... | [https://stac-extensions.github.io/storage/v2.... | 1.1.0 | Feature | 2019-10-02 17:48:16.589000+00:00 | 2019-10-02 17:56:47.166000+00:00 | 2019-10-02 17:48:16.589000+00:00 | {'aws': {'bucket': 'nsidc-cumulus-prod-protect... | 275 |
# Normalize colormap across both dataframes
vmin, vmax = gpd.pd.concat([gf_i.dayofyear, gf_is2.dayofyear]).agg(["min", "max"])
cmap = "plasma"
# NOTE: here we just take the boundary of the union of all maxar+lidar regions to avoid many overlay geometries
union = gpd.GeoDataFrame(geometry=[gf_i.union_all()], crs="EPSG:4326")
m = gf_i.explore(
column="dayofyear", cmap=cmap, vmin=vmin, vmax=vmax, style_kwds=dict(color="black")
)
gf_is2 = union.overlay(gf_is2, how="intersection")
gf_is2.explore(m=m, column="dayofyear", cmap=cmap, vmin=vmin, vmax=vmax, legend=False)
m
# For each Stereo pair, describe number of altimeter passes and day offset
stereo_pair = gf_i.iloc[[0]]
gf_dt = stereo_pair.overlay(gf_is2, how="intersection")
m = stereo_pair.explore(
column="dayofyear",
vmin=vmin,
vmax=vmax,
cmap=cmap,
legend=False,
style_kwds=dict(color="black"),
)
gf_dt.explore(m=m, column="dayofyear_2", vmin=vmin, vmax=vmax, cmap=cmap)
print("Maxar Stereo Acquisition DOY - ICESat-2 DOY")
for i in range(len(gf_i)):
stereo_pair = gf_i.iloc[[i]]
gf_dt = stereo_pair.overlay(gf_is2, how="intersection")
min_dt = (gf_dt.dayofyear_1 - gf_dt.dayofyear_2).min()
print(stereo_pair.stereo_pair_identifiers.values[0], min_dt)
Maxar Stereo Acquisition DOY - ICESat-2 DOY
['11a11283-6661-4f91-9bcb-5dab3c6a5d02-inv'] -2
['21d1d4b2-47a4-4c4d-a26f-5c688199b518-inv'] -18
['28b60c45-3164-4cd0-9b43-67dac4e8c7b4-inv'] -36
['41fef87b-0d7c-46c2-a598-bcd178dfb9e0-inv'] -30
['6002024a-ca85-4f3c-8c7f-ce849c175a52-inv'] 27
Summary#
Any of these Maxar stereo pairs could be worth ordering. But if the goal is to have coincident acquisitions as close as possible in time ‘11a11283-6661-4f91-9bcb-5dab3c6a5d02-inv’ seems like a good bet:
There is an overlapping ICESat-2 track 2 days later
There is an overlapping GEDI track 3 days earlier
3DEP Lidar was acquired 27 to 36 days earlier
# Can save this dataframe, or just note STAC ids to work with later ('102001008EC5AC00','102001008BE9BB00')
pair_id = "11a11283-6661-4f91-9bcb-5dab3c6a5d02-inv"
full_frame = gf_maxar[gf_maxar.stereo_pair_id == pair_id]
subset = gf_i[gf_i.stereo_pair_id == pair_id]
# Save the original STAC metadata and polygon
full_frame.to_parquet("/tmp/maxar-mono-images.parquet")
# Also save the subsetted region for this stereo pair
subset.to_parquet("/tmp/stereo-subset.parquet")