diff --git a/backend/src/Service/Ingestion/IngestionService.php b/backend/src/Service/Ingestion/IngestionService.php index ce0b21f..f5d6a89 100644 --- a/backend/src/Service/Ingestion/IngestionService.php +++ b/backend/src/Service/Ingestion/IngestionService.php @@ -234,7 +234,7 @@ class IngestionService return ['type' => 'MultiPolygon', 'coordinates' => $polygons]; } - private const MIN_POLYGON_AREA_M2 = 1_000_000; // 1 km² — filtre résidus AFAI + private const MIN_POLYGON_AREA_M2 = 3_000_000; // 3 km² — filtre bruit résiduel (aérosols, sun glint isolés) private function updateSpatialMeta(string $id): void { diff --git a/backend/src/Service/SentinelHub/SentinelHubClient.php b/backend/src/Service/SentinelHub/SentinelHubClient.php index e13e4d0..264aa4f 100644 --- a/backend/src/Service/SentinelHub/SentinelHubClient.php +++ b/backend/src/Service/SentinelHub/SentinelHubClient.php @@ -144,77 +144,91 @@ class SentinelHubClient private function getAfaiBinaryEvalscript(): string { - // Longueurs d'onde Sentinel-2 (nm) — constantes capteur - // B04 (Red) : 664.5 | B08 (NIR) : 832.8 | B11 (SWIR1) : 1613.7 + // Sentinel-2 — AFAI (Hu 2009) avec filtrage aérosol renforcé. // - // Seuil AFAI : 0.015 (relevé de 0.005) - // → 0.005 génère des faux positifs massifs (sun glint, eaux côtières turbides) - // → 0.015 correspond à une densité minimale réelle de Sargassum fluitans/natans - // selon Hu (2009), Wang & Hu (2016) : AFAI > 0.010-0.020 pour détection fiable + // Problème principal : brume de sable saharienne (mars–août en Caraïbe) + // → lève le bleu (B02 490nm) et crée de faux AFAI positifs. + // Solution : ratio B02/B04 élevé = signature aérosol, pas sargasse. + // + // Seuil AFAI relevé 0.020 → 0.025 pour meilleure spécificité sans + // sacrifier les événements réels (Wang & Hu 2016 : 0.020–0.030 recommandé). return <<<'EVALSCRIPT' //VERSION=3 function setup() { return { - input: [{ bands: ["B03", "B04", "B08", "B11", "CLP"], units: "REFLECTANCE" }], + input: [{ bands: ["B02", "B03", "B04", "B08", "B11", "CLP"], units: "REFLECTANCE" }], output: { bands: 1, sampleType: "UINT8" } }; } function evaluatePixel(sample) { - if (sample.CLP > 0.4) { return [255]; } // nodata : nuage - // Masque nuage if (sample.CLP > 0.3) { return [255]; } - // SWIR élevé = artifact atmo, cirrus fin, aérosol, eau côtière turbide + // SWIR élevé = cirrus fin, aérosol, eau côtière turbide if (sample.B11 > 0.03) { return [0]; } // NIR élevé = sun glint ou eau peu profonde if (sample.B08 > 0.06) { return [0]; } - // Masque eau : NDWI = (Green - NIR) / (Green + NIR) — terre si <= 0 + // Filtre brume de sable saharienne : + // - B02 absolu > 0.08 : bleu trop élevé pour de l'eau propre ou des sargasses + // - B02/B04 > 2.5 : ratio bleu/rouge typique des aérosols continentaux + // (sargasses : ratio ≈ 0.8–1.2 | poussière sahélienne : ratio ≈ 2.5–5) + if (sample.B02 > 0.08) { return [0]; } + if (sample.B04 > 0.001 && (sample.B02 / sample.B04) > 2.5) { return [0]; } + + // Masque eau : NDWI = (Green - NIR) / (Green + NIR) const ndwiDenom = sample.B03 + sample.B08; if (ndwiDenom === 0) { return [0]; } const ndwi = (sample.B03 - sample.B08) / ndwiDenom; - if (ndwi <= 0.05) { return [0]; } // marge pour eau côtière peu profonde + if (ndwi <= 0.05) { return [0]; } + // AFAI — Hu 2009 const lambdaRed = 664.5; const lambdaNir = 832.8; const lambdaSwir = 1613.7; const ratio = (lambdaNir - lambdaRed) / (lambdaSwir - lambdaRed); - const afai = sample.B08 - sample.B04 - (sample.B11 - sample.B04) * ratio; - // Seuil 0.020 : calibré pour Sargassum fluitans/natans en Caraïbe - // (Hu 2009 recommande post-filtrage spatial que l'on fait via PostGIS) - return [afai > 0.020 ? 1 : 0]; + return [afai > 0.025 ? 1 : 0]; } EVALSCRIPT; } /** - * Evalscript Sentinel-3 OLCI — FAI (Floating Algae Index) via MCI. + * Evalscript Sentinel-3 OLCI — MCI (Maximum Chlorophyll Index) adapté sargasses. * - * OLCI bands (nm) : Oa08=665 (Red), Oa11=708.75 (RedEdge), Oa17=865 (NIR) - * MCI = Oa17 − Oa08 − (Oa11 − Oa08) × (865−665)/(708.75−665) - * Seuil empirique retenu : MCI > 0.008 + * 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 { - // Seuil MCI : 0.015 (relevé de 0.008) - // → même logique que S2 : 0.008 trop sensible pour la Caraïbe return <<<'EVALSCRIPT' //VERSION=3 function setup() { return { - input: [{ bands: ["B06", "B08", "B11", "B17"], units: "REFLECTANCE" }], + 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.06) { return [0]; } + 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; @@ -222,14 +236,15 @@ function evaluatePixel(sample) { 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; - return [mci > 0.020 ? 1 : 0]; + // Seuil 0.030 (vs 0.020) : compense l'absence de SWIR pour meilleure spécificité + return [mci > 0.030 ? 1 : 0]; } EVALSCRIPT; }