A search queries the CDSE catalog for the bursts of a place and a time window, and a download fetches them. Searching needs no account and downloads nothing, so you can refine a query freely before committing to a download.
Search an area
Pass an area of interest as a shapely geometry in longitude and latitude, together with a time window. The search returns every burst that overlaps the area and shows their footprints on a map:
from shapely.geometry import box
from s1bursts import BurstCatalog, FileManager, SearchQuery, TimeRange
catalog = BurstCatalog(FileManager("data"))
bursts = catalog.search(SearchQuery(
intersects=box(8.00, 46.40, 8.08, 46.48),
time_range=TimeRange("2022-01-01", "2022-01-31"),
polarization="VV",
))
bursts
BurstCollection
Bursts
|
burst_id |
sub_swath |
polarization |
date |
| 0 |
140414 |
IW2 |
VV |
2022-01-01 05:35:41.647945+00:00 |
| 1 |
187142 |
IW3 |
VV |
2022-01-02 17:23:51.341251+00:00 |
| 2 |
187143 |
IW3 |
VV |
2022-01-02 17:23:54.099528+00:00 |
| 3 |
297211 |
IW1 |
VV |
2022-01-06 05:43:49.467183+00:00 |
| 4 |
297212 |
IW1 |
VV |
2022-01-06 05:43:52.225460+00:00 |
| 5 |
30345 |
IW1 |
VV |
2022-01-09 17:15:40.311026+00:00 |
| 6 |
30346 |
IW1 |
VV |
2022-01-09 17:15:43.069303+00:00 |
| 7 |
140414 |
IW2 |
VV |
2022-01-13 05:35:41.278530+00:00 |
| 8 |
187142 |
IW3 |
VV |
2022-01-14 17:23:50.966877+00:00 |
| 9 |
187143 |
IW3 |
VV |
2022-01-14 17:23:53.725154+00:00 |
| 10 |
297211 |
IW1 |
VV |
2022-01-18 05:43:48.973546+00:00 |
| 11 |
297212 |
IW1 |
VV |
2022-01-18 05:43:51.731823+00:00 |
| 12 |
30345 |
IW1 |
VV |
2022-01-21 17:15:39.615073+00:00 |
| 13 |
30346 |
IW1 |
VV |
2022-01-21 17:15:42.373350+00:00 |
| 14 |
140414 |
IW2 |
VV |
2022-01-25 05:35:40.663418+00:00 |
| 15 |
187142 |
IW3 |
VV |
2022-01-26 17:23:50.388553+00:00 |
| 16 |
187143 |
IW3 |
VV |
2022-01-26 17:23:53.146830+00:00 |
| 17 |
297211 |
IW1 |
VV |
2022-01-30 05:43:48.503805+00:00 |
| 18 |
297212 |
IW1 |
VV |
2022-01-30 05:43:51.262082+00:00 |
Even a small area usually returns several footprints. Bursts from different orbits and sub-swaths overlap the same few kilometers, each seeing the ground from a different direction. The area selects whole bursts and does not crop them. A TimeRange covers whole days, and both end dates are included.
Combine searches
Search results add up with +, which drops any duplicates. Use this to collect several searches into one download, for example the same footprint in winter and in summer:
footprint = dict(burst_id=30346, sub_swath="IW1", polarization="VV")
winter = catalog.search(SearchQuery(time_range=TimeRange("2022-01-01", "2022-01-31"), **footprint))
summer = catalog.search(SearchQuery(time_range=TimeRange("2022-07-01", "2022-07-31"), **footprint))
both = winter + summer
print(len(winter), len(summer), len(both))
Select part of a search
A search result can be narrowed without a new query. Each selection returns a new collection, so they can be chained:
within(time_range) |
Acquired on a day of a TimeRange |
intersecting(geometry) |
Whose footprint touches a geometry, such as an area or an MGRS grid square |
filter(**fields) |
Whose fields equal a value, or any value of a list |
january = bursts.within(TimeRange("2022-01-01", "2022-01-31"))
descending = bursts.filter(pass_direction="Descending", rel_orbit_number=[66, 139])
Pair acquisitions
Coherence compares two acquisitions of the same footprint. find_pairs() turns a search result into pairs a fixed number of days apart, which can be downloaded and processed like single bursts:
pairs = bursts.find_pairs(baseline=12)
The baseline is a whole number of days, and both arguments are passed by keyword. max_delta sets how far the actual spacing may deviate from it, six hours by default. A pair needs both of its acquisitions, so search a little beyond the end of the period you want and keep the pairs that start within it:
pairs = bursts.find_pairs(baseline=12).starting_within(january)
Save a search
Save a search result if you want to rerun an analysis on exactly the same acquisitions later. The records live in a GeoDataFrame, which can be written to GeoParquet and loaded again without querying the catalog:
import geopandas as gpd
from s1bursts.burst import BurstCollection
bursts.df.to_parquet("inventory.parquet")
bursts = BurstCollection(gpd.read_parquet("inventory.parquet"))
Download the bursts
BurstCatalog.download() fetches the bursts of a search result, or both acquisitions of every pair. Bursts already on disk are skipped, so an interrupted download can be run again:
summary = catalog.download(bursts, max_workers=4)
summary.print_summary()
max_workers sets how many bursts download in parallel. Each file is saved in the s1-slc folder below the FileManager’s base directory.
When the burst service fails
CDSE’s burst service occasionally fails for some bursts. If CDSE S3 keys are set, s1bursts then extracts the burst from the full scene on CDSE’s S3 storage instead. This route is slower but usually succeeds. Without the keys, the burst is recorded as failed. Pass endpoint="s3" to try the S3 route first.
Failed downloads
A failed burst does not stop the download by default. It is recorded in the returned OperationSummary and the remaining bursts continue. Check the summary before processing: raise_on_failure() raises an OperationFailedError that lists every failed burst, and otherwise returns the summary, so it can follow the call directly:
catalog.download(bursts).raise_on_failure()
To inspect the failures yourself, read them from the summary:
for failure in summary.failed_items:
print(failure.source.date, failure.error)
Most failures are temporary server errors and succeed on a second run, which downloads only the missing bursts. To stop at the first failure instead, create the catalog with BurstCatalog(fm, continue_on_error=False). A burst that CDSE reports as missing is not requested again by the same BurstCatalog. Create a new one to retry it later. To accept such bursts and continue, ignore their error type:
from s1bursts.errors import BurstNotFoundError
catalog.download(bursts).raise_on_failure(ignore=BurstNotFoundError)
Reuse downloaded bursts
FileManager lists what is already stored, without a new search. Use this to process bursts downloaded in an earlier session:
from s1bursts import FileManager
from s1bursts.burst import Burst
fm = FileManager("data")
bursts = fm.find(Burst, burst_id=30346, time_range=TimeRange("2022-01-01", "2022-06-30"))
The result is a list of bursts, which the processing methods accept as it is. To form pairs, turn it into a collection first with BurstCollection.from_bursts(bursts).
find() lists every product and composite type the same way: pass the type, such as RTCProduct or RTCLRWComposite from s1bursts.product, and filter on the fields its filename records. These listings are rebuilt from filenames, so they contain less information than the original search result. Save the search if you need the full catalog record.
The burst-ID map
A burst from a search includes its relative orbit, pass direction and footprint, and product and SLC filenames record the orbit and pass direction, so the usual search, download, process and composite workflow needs no further data. Only a burst that did not come from a search needs to look these up, in the burst-ID map published by SAR-MPC. That applies to a burst you construct by hand, and to the footprint of a burst found with find(Burst), which its filename does not record. Download the map once, about 500 MB per acquisition mode, when you need it:
from s1bursts import download_burst_map
download_burst_map(mode="IW")