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
|
* Ingestion Sentinel-2 uniquement.
|
||||||
* couverture nuageuse est trop élevée ou si la scène S2 est indisponible.
|
* 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
|
public function ingestWithFallback(array $bbox, \DateTimeImmutable $date): void
|
||||||
{
|
{
|
||||||
try {
|
$this->ingest($bbox, $date, 'Sentinel-2', 'sentinel-2-l2a');
|
||||||
$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 */
|
/** @param array<int, float> $bbox */
|
||||||
|
|||||||
@@ -45,18 +45,12 @@ class SentinelHubClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retourne la couverture nuageuse (%) de la meilleure scène disponible.
|
* Retourne la couverture nuageuse (%) de la meilleure scène Sentinel-2 disponible.
|
||||||
* collection : 'sentinel-2-l2a' (défaut) ou 'sentinel-3-olci'
|
*
|
||||||
|
* @param array<int, float> $bbox
|
||||||
*/
|
*/
|
||||||
/** @param array<int, float> $bbox */
|
|
||||||
public function getCloudCoverage(array $bbox, \DateTimeImmutable $date, string $collection = 'sentinel-2-l2a'): float
|
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();
|
$token = $this->getToken();
|
||||||
$dateStr = $date->format('Y-m-d');
|
$dateStr = $date->format('Y-m-d');
|
||||||
|
|
||||||
@@ -97,9 +91,7 @@ class SentinelHubClient
|
|||||||
$token = $this->getToken();
|
$token = $this->getToken();
|
||||||
$dateStr = $date->format('Y-m-d');
|
$dateStr = $date->format('Y-m-d');
|
||||||
|
|
||||||
$evalscript = $collection === 'sentinel-3-olci'
|
$evalscript = $this->getAfaiBinaryEvalscript();
|
||||||
? $this->getFaiBinaryEvalscriptS3()
|
|
||||||
: $this->getAfaiBinaryEvalscript();
|
|
||||||
|
|
||||||
$response = $this->httpClient->request('POST', self::PROCESS_URL, [
|
$response = $this->httpClient->request('POST', self::PROCESS_URL, [
|
||||||
'headers' => ['Authorization' => "Bearer {$token}"],
|
'headers' => ['Authorization' => "Bearer {$token}"],
|
||||||
@@ -196,56 +188,4 @@ function evaluatePixel(sample) {
|
|||||||
EVALSCRIPT;
|
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