find_repeat_sites
Rank the archive's most repeat-imaged sites — where change detection has something to measure.
The discovery step *before* every analysis verb. ``change`` / ``timescan`` /
``stack`` / ``pick_change_interval`` / ``narrate_change`` all answer *what*
changed at a site, but each assumes the caller already knows *which* site to
point them at. Umbra files every pass of an area under one task (site), so a
site's coverage is just how many dated passes share its task; the sites with
the most are exactly the ones where a time series exists to analyse. This
tool ranks them, so ``find_repeat_sites → pick_change_interval →
narrate_change`` is a complete chain a model can drive with no site known in
advance — the deterministic answer to "where is there something to look at?"
(the discovery moat; no model is called). Reach for this
(or ``search_catalog(area=..., fuzzy=True)``) before ``semantic=True`` when
the site name is roughly known.
The filters scope the *pool* of acquisitions the ranking is computed over,
and are the same ones ``search_catalog`` takes: ``bbox``
(``[min_lon, min_lat, max_lon, max_lat]`` WGS84), ``intersects`` (a GeoJSON
polygon, mutually exclusive with ``bbox`` / ``place``), ``place`` (free-text,
geocoded to a bbox), ``area`` (a task-name substring, loosened by ``fuzzy``),
``start`` / ``end`` date bounds, ``products`` (GEC / SICD / SIDD / CPHD), and
the SAR property filters ``polarizations`` / ``min_incidence`` /
``max_incidence`` / ``max_resolution``. ``top`` caps how many sites are
returned, and ``min_passes`` is how many passes a site needs to qualify (2 is
the minimum a change composite can use; raise it to find only deeply-revisited
series), counted on the depth ``rank_by`` measures -- raw dated passes by
default, the usable (comparable) series' depth under ``rank_by="comparable"``.
``rank_by`` chooses the order: ``"passes"`` (the default) ranks by raw pass
count; ``"comparable"`` ranks by *analysable* depth instead -- the
``comparable_passes`` largest same-polarization dated subset a change verb can
actually difference -- so a deep single-polarization series is not outranked by
a broader mixed-polarization site whose passes no analysis verb can compare, and
``min_passes`` then floors that same usable depth, so ``rank_by="comparable",
min_passes=3`` returns only sites whose differenceable series is at least three
passes deep. The two agree when every dated pass of every site shares one
polarization; prefer ``"comparable"`` when the next step is
``pick_change_interval`` / ``stack_stats``, which consume exactly that
differenceable subset. Three *temporal* orders rank on the axes the filters below
select on: ``"recency"`` orders by each site's newest dated pass (the
still-active site to monitor or task, which a depth order buries under a deeper
but dormant series), ``"span"`` by each site's observation baseline (the site
watched long enough for slow change to show), and ``"cadence"`` by each site's
*typical* revisit gap, tightest first (the most-frequently-imaged site -- the
median gap, not the worst one, so a single outage does not bury an otherwise
reliably-imaged series; the worst-case reading stays the ``max_revisit_days``
filter). ``min_passes`` still qualifies a site on raw depth under any of the
three, so a temporal order returns only sites with a series worth ordering.
``active_since`` keeps only sites still imaged *on or after* that date -- a
recency filter on each site's newest pass, so a deep series that stopped long
ago is dropped and an actively-revisited one is kept. Use it to find live
monitoring targets ("repeat-imaged sites still being collected"). It accepts an
ISO date (``"2024-06-01"``), a bare year/month, or a relative expression
(``"6 months ago"``), is orthogonal to ``rank_by`` / ``min_passes`` (it gates on
the site's latest pass, whatever depth the ranking measures), and differs from
``start`` / ``end``: those bound which *passes* enter the pool (truncating every
series to a window), whereas this selects whole sites by recency and keeps each
survivor's full history.
``active_before`` is the complement -- keep only sites whose newest pass is *on
or before* that date (a dormant series that stopped imaging). Pass both to select
sites whose latest pass falls *within* a window (``active_since <= last <=
active_before``). Same grammar as ``active_since``, but a bare year/month covers
the whole named period (``active_before="2024"`` is "last imaged on or before
2024-12-31"), symmetric with ``end``.
``first_since`` / ``first_before`` are the onset (first-seen) twins of the
``active_*`` pair -- they gate each site's *earliest* pass rather than its newest.
``first_since`` keeps **newly-appeared** series (first imaged on or after the date --
ones that entered the archive recently) and ``first_before`` keeps
**long-established** ones (first imaged on or before it -- watched since before
then); pass both to bound the onset to a window
(``first_since <= first <= first_before``), exactly as the ``active_*`` pair bounds
the newest pass. Same grammar as ``active_since``, ``first_before`` snapping a bare
year/month to its last day. Orthogonal to the ``active_*`` recency filters, since
when a site started and whether it is still going are independent axes -- so
``first_since=X, active_before=Y`` finds series that appeared after X and have
already gone dormant by Y. ``None`` (the default) applies no onset filter.
``max_revisit_days`` keeps only sites revisited *at least this often* -- a cadence
filter on each site's **worst-case** revisit gap (in days), so a series with any
stretch longer than that between consecutive passes is dropped and a reliably
imaged one kept. Use it to find monitorable targets ("repeat-imaged sites with no
blind spot longer than N days"). It gates the cadence ``rank_by`` measures -- the
whole dated series by default, the usable (comparable) series' worst gap under
``rank_by="comparable"`` -- is orthogonal to ``active_since`` / ``active_before``,
and drops a site with fewer than two passes in the gated series. A non-positive
value is a ``ValueError``.
``median_revisit_days`` is the *typical*-cadence twin of ``max_revisit_days`` --
keep only sites whose **median** revisit gap (in days) is at most that, so a site
*usually* imaged often is kept even if a single stretch runs long, where
``max_revisit_days`` drops it the moment any gap exceeds the bound. Use it for
"usually imaged frequently" rather than "never blind for longer than N days"; set
both to combine the two. It gates the cadence ``rank_by`` measures (the usable
series' typical gap under ``rank_by="comparable"``), is orthogonal to
``max_revisit_days`` and the recency filters, and drops a site with fewer than two
passes in the gated series. A non-positive value is a ``ValueError``.
``min_span_days`` keeps only sites imaged over *at least this long* -- a baseline
filter on each site's observation **span** (in days, first pass to last), so a
series confined to a short window is dropped and a long-baseline one kept. Use it
to find targets for *slow* change (subsidence, construction, deforestation) that
needs a long observation window to be visible. It is a different axis from
``max_revisit_days``: cadence is the worst *gap* (how reliably a site is watched),
span is the total *baseline* (how long it has been watched at all). It gates the
span ``rank_by`` measures -- the whole dated series by default, the usable
(comparable) series' span under ``rank_by="comparable"`` -- is orthogonal to
``active_since`` / ``active_before`` / ``max_revisit_days``, and drops a site with
fewer than two passes in the gated series. A non-positive value is a ``ValueError``.
``max_span_days`` is the upper twin of ``min_span_days`` -- keep only sites imaged
over *at most this long* (a short-lived series, a burst of imaging now over), the
complement of the floor. Set with ``min_span_days`` the two bound each site's
baseline to a window (``min_span_days <= span <= max_span_days``), exactly as
``active_since`` / ``active_before`` bound the newest pass to one. It gates the same
span ``rank_by`` measures (the usable series under ``rank_by="comparable"``) and,
like the floor, drops a site with no measurable span so the window admits only a
confirmed baseline. A non-positive value is a ``ValueError``.
``limit`` sizes the *live/token* pool only. On the local index a site's depth
is measured **whole-archive** — one ``GROUP BY task`` over the entire index
(:meth:`CatalogIndex.rank_sites`), so a deeply-imaged site is ranked by all
its passes rather than by whatever the first ``limit`` rows admitted, and
``limit`` does not apply. On the live open bucket or the Canopy archive there
is no index to group over, so ``limit`` sizes the single re-listed pool the
ranking is computed from (bigger finds more sites and deeper series but scans
more of the archive; ``area`` narrows it instead).
Returns ``sites``, best-covered first: each a ``SiteCoverage`` with the site's
``task`` codename and place ``label``, its ``passes`` count, ``first`` /
``last`` dates and ``span_days``, ``min_revisit_days`` / ``median_revisit_days``
/ ``max_revisit_days`` cadence (the last being the longest gap, i.e. the site's
worst-case temporal resolution) with ``comparable_min_revisit_days`` /
``comparable_median_revisit_days`` / ``comparable_max_revisit_days`` the same
shortest / typical / worst triple over just the differenceable subset (each can
diverge from its raw twin when a cross-polarization pass fills a gap the
analysis verbs cannot use), union ``bbox`` footprint, ``products`` and
``polarizations`` — with ``comparable_polarizations`` naming *which* single
polarization the usable series is (the signature the ``comparable_*`` figures
are all measured over, a strict subset of ``polarizations`` when the site is
mixed) — and the
``hrefs`` — the site's pass STAC URLs, **oldest-first**, ready to hand straight
to ``pick_change_interval`` (or ``change_composite`` / ``stack_stats``). Also
``count`` (sites returned), the pool ``source``, the resolved
``place`` / ``bbox`` / ``area``, a ``query`` echo of the ranking-and-selection
inputs (``rank_by``, ``top``, ``min_passes`` and the recency / onset / cadence /
baseline bounds, as you passed them) so the answer records how it was ranked and
filtered, and the attribution line.
``local`` selects the backend exactly as ``search_catalog`` does — unset uses
the on-disk index when present (instant) and a live S3 walk otherwise. With a
Canopy token configured (``$UMBRA_CANOPY_TOKEN``) the pool is drawn from the
commercial archive instead. The ranking is single-sourced with the static
showcase's featured-gallery selector, so this and ``umbra sites`` cannot
disagree about what "most repeat-imaged" means.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| end | No | ||
| top | No | ||
| area | No | ||
| bbox | No | ||
| fuzzy | No | ||
| limit | No | ||
| local | No | ||
| place | No | ||
| start | No | ||
| rank_by | No | passes | |
| products | No | ||
| intersects | No | ||
| min_passes | No | ||
| first_since | No | ||
| active_since | No | ||
| first_before | No | ||
| active_before | No | ||
| max_incidence | No | ||
| max_span_days | No | ||
| min_incidence | No | ||
| min_span_days | No | ||
| polarizations | No | ||
| max_resolution | No | ||
| max_revisit_days | No | ||
| median_revisit_days | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||