remove: fallback Sentinel-3 — pas de données vaut mieux que du bruit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,25 +28,16 @@ class IngestionService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Tente l'ingestion Sentinel-2, puis Sentinel-3 OLCI en fallback si la
|
||||
* couverture nuageuse est trop élevée ou si la scène S2 est indisponible.
|
||||
* Ingestion Sentinel-2 uniquement.
|
||||
* Pas de fallback S3 : sans bande SWIR, S3 OLCI génère trop de faux positifs
|
||||
* (brume de sable, sun glint) pour être exploitable en production.
|
||||
* Préférer aucune donnée à de mauvaises données.
|
||||
*
|
||||
* @param array<int, float> $bbox
|
||||
*/
|
||||
/** @param array<int, float> $bbox */
|
||||
public function ingestWithFallback(array $bbox, \DateTimeImmutable $date): void
|
||||
{
|
||||
try {
|
||||
$this->ingest($bbox, $date, 'Sentinel-2', 'sentinel-2-l2a');
|
||||
} catch (HighCloudCoverageException $e) {
|
||||
$this->logger->info('S2 cloud too high, falling back to Sentinel-3', ['reason' => $e->getMessage()]);
|
||||
$this->ingest($bbox, $date, 'Sentinel-3', 'sentinel-3-olci');
|
||||
} catch (\Throwable $e) {
|
||||
// Erreur S2 non liée à la couverture nuageuse (HTTP 4xx/5xx, timeout…)
|
||||
// → tenter S3 plutôt que perdre tout le cycle pour cette zone
|
||||
$this->logger->warning('S2 ingestion error, falling back to Sentinel-3', [
|
||||
'reason' => $e->getMessage(),
|
||||
]);
|
||||
$this->ingest($bbox, $date, 'Sentinel-3', 'sentinel-3-olci');
|
||||
}
|
||||
}
|
||||
|
||||
/** @param array<int, float> $bbox */
|
||||
|
||||
@@ -45,18 +45,12 @@ class SentinelHubClient
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la couverture nuageuse (%) de la meilleure scène disponible.
|
||||
* collection : 'sentinel-2-l2a' (défaut) ou 'sentinel-3-olci'
|
||||
* Retourne la couverture nuageuse (%) de la meilleure scène Sentinel-2 disponible.
|
||||
*
|
||||
* @param array<int, float> $bbox
|
||||
*/
|
||||
/** @param array<int, float> $bbox */
|
||||
public function getCloudCoverage(array $bbox, \DateTimeImmutable $date, string $collection = 'sentinel-2-l2a'): float
|
||||
{
|
||||
// Sentinel-3 OLCI n'expose pas eo:cloud_cover dans le Catalog ; on renvoie 0
|
||||
// pour laisser le Process API décider.
|
||||
if ($collection === 'sentinel-3-olci') {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$token = $this->getToken();
|
||||
$dateStr = $date->format('Y-m-d');
|
||||
|
||||
@@ -97,9 +91,7 @@ class SentinelHubClient
|
||||
$token = $this->getToken();
|
||||
$dateStr = $date->format('Y-m-d');
|
||||
|
||||
$evalscript = $collection === 'sentinel-3-olci'
|
||||
? $this->getFaiBinaryEvalscriptS3()
|
||||
: $this->getAfaiBinaryEvalscript();
|
||||
$evalscript = $this->getAfaiBinaryEvalscript();
|
||||
|
||||
$response = $this->httpClient->request('POST', self::PROCESS_URL, [
|
||||
'headers' => ['Authorization' => "Bearer {$token}"],
|
||||
@@ -196,56 +188,4 @@ function evaluatePixel(sample) {
|
||||
EVALSCRIPT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evalscript Sentinel-3 OLCI — MCI (Maximum Chlorophyll Index) adapté sargasses.
|
||||
*
|
||||
* OLCI bands (nm) : B03=442.5 (Bleu), B06=560 (Vert), B08=665 (Rouge),
|
||||
* B11=708.75 (RedEdge), B17=865 (NIR)
|
||||
*
|
||||
* Limite fondamentale : S3 OLCI n'a pas de bande SWIR → impossible de filtrer
|
||||
* les aérosols comme S2. La bande bleue B03 (442nm) compense partiellement :
|
||||
* la poussière saharienne a un ratio bleu/rouge >> 2 (sargasses : ≈ 0.7–1.0).
|
||||
*
|
||||
* Seuil MCI relevé 0.020 → 0.030 pour compenser l'absence de SWIR.
|
||||
*/
|
||||
private function getFaiBinaryEvalscriptS3(): string
|
||||
{
|
||||
return <<<'EVALSCRIPT'
|
||||
//VERSION=3
|
||||
function setup() {
|
||||
return {
|
||||
input: [{ bands: ["B03", "B06", "B08", "B11", "B17"], units: "REFLECTANCE" }],
|
||||
output: { bands: 1, sampleType: "UINT8" }
|
||||
};
|
||||
}
|
||||
|
||||
function evaluatePixel(sample) {
|
||||
// NIR élevé = sun glint
|
||||
if (sample.B17 > 0.05) { return [0]; }
|
||||
|
||||
// Filtre brume de sable via bande bleue B03 (442.5nm) :
|
||||
// - bleu absolu > 0.08 : atmosphère trop chargée en aérosols
|
||||
// - B03/B08 (bleu/rouge) > 2.0 : signature poussière saharienne
|
||||
// (sargasses : ratio ≈ 0.7 | brume sahélienne : ratio > 2)
|
||||
if (sample.B03 > 0.08) { return [0]; }
|
||||
if (sample.B08 > 0.001 && (sample.B03 / sample.B08) > 2.0) { return [0]; }
|
||||
|
||||
// Masque eau : NDWI = (Green B06 560nm - NIR B17 865nm) / (Green + NIR)
|
||||
const ndwiDenom = sample.B06 + sample.B17;
|
||||
if (ndwiDenom === 0) { return [0]; }
|
||||
const ndwi = (sample.B06 - sample.B17) / ndwiDenom;
|
||||
if (ndwi <= 0.05) { return [0]; }
|
||||
|
||||
// MCI — baseline interpolée entre rouge (B08) et redEdge (B11)
|
||||
const lambdaRed = 665.0;
|
||||
const lambdaRedEdge = 708.75;
|
||||
const lambdaNir = 865.0;
|
||||
const baseline = sample.B08 + (sample.B11 - sample.B08) * (lambdaNir - lambdaRed) / (lambdaRedEdge - lambdaRed);
|
||||
const mci = sample.B17 - baseline;
|
||||
|
||||
// Seuil 0.030 (vs 0.020) : compense l'absence de SWIR pour meilleure spécificité
|
||||
return [mci > 0.030 ? 1 : 0];
|
||||
}
|
||||
EVALSCRIPT;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user