.. _insitu_matchups:

##########################
Satellite–in-situ matchups
##########################

Besides pairing two satellites (see :doc:`finding_matchups`), eomatch can match
satellite products against a fixed *in-situ* (ground-based) site, such as a
RadCalNet or HYPERNETS station. The result is the same
:py:class:`~eomatch.MatchupEvent` / :py:class:`~eomatch.Matchup` objects, so
cataloguing, enrichment and dataset building work unchanged.

How it differs from satellite–satellite matching
################################################

- There is no orbit propagation. The site's fixed location, not a crossover,
  defines where to search.
- :py:class:`~eomatch.finder.sat2insitu.Sat2InSituMUFinder` queries the
  satellite collection over the site's registered region of interest for the
  whole configured time window, then groups the returned products into events:
  passes closer together than ``time_diff_threshold`` form one event.
- For each event, every satellite product is paired with the in-situ products
  that are within ``time_diff_threshold`` of it and overlap its footprint.
  Products are compared as time *intervals*, so a satellite pass anywhere inside
  an in-situ product's multi-hour recording window counts as a zero gap. A
  satellite product with no in-situ product close enough in time gets no
  matchup.

Configuration
#############

``platforms`` and ``collections`` are paired by position, and must contain
exactly one satellite and one in-situ collection. The platform paired with the
in-situ collection is the **site name**:

.. code-block:: yaml

   platforms: S2A, GONA
   collections: S2_MSI_L1C, RCN_BOA
   start_time: "2024-05-21 00:00:00"
   end_time:   "2024-05-24 00:00:00"
   time_diff_threshold: 1800          # seconds

``provider_by_collection`` (see :ref:`provider_pinning`) applies to the
satellite query as it does for satellite–satellite matching.

Running the finder
##################

.. code-block:: python

   from eomatch import EOMatchContext
   from eomatch.find_and_catalogue import find_and_catalogue_insitu

.. ipython:: python
   :verbatim:

   In [1]: ctx = EOMatchContext("insitu_config.yaml")

   In [2]: catalogue = find_and_catalogue_insitu(context=ctx, path="/data/insitu_catalogue")

   In [3]: event = catalogue.get_events()[0]

   In [4]: [p.collection for p in event.matchup_set[0].products]
   Out[4]: ['S2_MSI_L1C', 'RCN_BOA']

:py:func:`~eomatch.find_and_catalogue.find_and_catalogue_insitu` is the
in-situ counterpart of :py:func:`~eomatch.find_and_catalogue.find_and_catalogue`:
it runs the finder and saves the events and matchups to a STAC catalogue. To
work with the events without cataloguing, use the finder directly:

.. code-block:: python

   from eomatch.finder.sat2insitu import Sat2InSituMUFinder

   events = Sat2InSituMUFinder(context=ctx).finder()

Filtering on in-situ data quality
#################################

A matchup passing the time and footprint checks can still have no valid in-situ
sample near the crossover. Add the
:py:mod:`~eomatch.enrich.insitu_time_diff` enricher (see :doc:`enrichment`) to
record the gap to the nearest valid sample, then filter on it:

.. code-block:: python

   from eomatch.enrich import enrich
   from eomatch.enrich.insitu_time_diff import insitu_time_diff

.. ipython:: python
   :verbatim:

   In [1]: enrich(catalogue, enrichers=[insitu_time_diff])
   Out[1]: 12

   In [2]: nearby = {"insitu_time_diff_nearest_s": {"lt": 1800}}

   In [3]: events = catalogue.get_events(properties=nearby)
