Visualizzazione post con etichetta Google Earth Engine. Mostra tutti i post
Visualizzazione post con etichetta Google Earth Engine. Mostra tutti i post

lunedì 16 febbraio 2026

Telerilevamento plastiche in mare

Bibliografia

Biermann, L., Clewley, D., Martinez-Vicente, V. et al. Finding Plastic Patches in Coastal Waters using Optical Satellite Data. Sci Rep 10, 5364 (2020). https://doi.org/10.1038/s41598-020-62298-z 

Moshtaghi, M., Knaeps, E., Sterckx, S. et al. Spectral reflectance of marine macroplastics in the VNIR and SWIR measured in a controlled environment. Sci Rep 11, 5436 (2021). https://doi.org/10.1038/s41598-021-84867-6

MARIDA: A benchmark for Marine Debris detection from Sentinel-2 remote sensing data Katerina Kikaki ,Ioannis Kakogeorgiou, Paraskevi Mikeli, Dionysios E. Raitsos, Konstantinos Karantzalos PLOS Published: January 7, 2022 https://doi.org/10.1371/journal.pone.0262247  

Microplastiche in acqua

Alessio Monnanni, Valentina Rimondi, Guia Morelli, Alessia Nannoni, Alessandra Cincinelli, Tania Martellini, David Chelazzi, Marco Laurati, Laura Sforzi, Francesco Ciani, Pierfranco Lattanzi, Pilario Costagliola,
Microplastics and microfibers contamination in the Arno River (Central Italy): Impact from urban areas and contribution to the Mediterranean Sea,
Science of The Total Environment,
Volume 955,
2024,
177113,
ISSN 0048-9697,
https://doi.org/10.1016/j.scitotenv.2024.177113.
(https://www.sciencedirect.com/science/article/pii/S004896972407270X) 

Microplastiche in sedimenti

Types, occurrence and distribution of microplastics in sediments from the northern Tyrrhenian Se Michele Mistri a, Marco Scoponi b, Tommaso Granata c, Letizia Moruzzi c, Francesca Massara d, Cristina Munari a https://doi.org/10.1016/j.marpolbul.2020.111016

 

Annotazione : le plastiche in acqua si presentano alterate per la permanenza nel liquido. Inoltre la firma spettrale e' profondamente influenzata dalla presenza anche di una pellicola d'acqua sulla plastica galleggiante . Per quanto riguarda la sezione SWIR dello spettro la presenza di sedimenti puo' disturbare la risposta degli indici spettrali di calcolo

----------------------------------------------- 

Per Landsat 8 il metodo comune per determinare le plastiche e'determinato dall'indice

  Plastic Index  = Band 5 (NIR) / (Band 5 + Band 6 (SWIR1))


basato sul fatto che la risposta spettrale delle plastiche ha un picco nell'infrarosso vicino e un assorbimento nello SWIR

 


 

 var areaOfInterest =

    /* color: #98ff00 */
    /* shown: false */
    ee.Geometry.MultiPolygon(
        [[[[9.950883097809994, 43.88872579729536],
           [9.934403605622494, 43.48554689334167],
           [10.292832560700619, 43.51443524985007],
           [10.269486613434994, 43.53634133439625],
           [10.263993449372494, 43.62985044452412],
           [10.259873576325619, 43.7311534518937],
           [10.253007121247494, 43.76289953785138],
           [10.182969279450619, 43.910495519373605]]],
         [[[9.911900455707178, 43.92257421254923],
           [9.872075016254053, 43.45486023186587],
           [10.347233707660303, 43.44688454959106],
           [10.314274723285303, 43.50169612390543],
           [10.299168522113428, 43.51862782505474],
           [10.268956119769678, 43.55048637670663],
           [10.277195865863428, 43.565414278891666],
           [10.288182193988428, 43.57735393781841],
           [10.281315738910303, 43.60918145612981],
           [10.268956119769678, 43.656891171520144],
           [10.273075992816553, 43.70158462959755],
           [10.266209537738428, 43.73334638914901],
           [10.262089664691553, 43.78492333929173],
           [10.238743717425928, 43.83843695830995],
           [10.212651188129053, 43.89289227846965]]]]);


// 2. Pre-processing Function for Landsat 8
var processL8 = function(image) {
  var opticalBands = image.select('SR_B.*').multiply(0.0000275).add(-0.2);
  
  // Plastic Index Formula: NIR / (NIR + SWIR1)
  // Landsat 8: B5 is NIR, B6 is SWIR1
  var plasticIndex = opticalBands.expression(
    'NIR / (NIR + SWIR1)', {
      'NIR': opticalBands.select('SR_B5'),
      'SWIR1': opticalBands.select('SR_B6')
    }).rename('PI');
    
  return image.addBands(plasticIndex).select('PI');
};

// 3. Load 2025 Median Plastic Index
var pi2025 = ee.ImageCollection("LANDSAT/LC08/C02/T1_L2")
  .filterBounds(areaOfInterest)
  .filterDate('2025-01-01', '2025-12-31')
  .map(processL8)
  .median()
  .clip(areaOfInterest);

// 4. Visualization Parameters
// Plastic usually results in higher values in this index (closer to 0.4 - 0.6)
var piViz = {
  min: 0, 
  max: 0.6, 
  palette: ['#0000FF', '#00FFFF', '#FFFF00', '#FF0000'] // Blue to Red
};

// 5. Display the Map
Map.addLayer(pi2025, piViz, 'Plastic Index Median 2025');

// --- 6. ADDING THE LEGEND ---

var legend = ui.Panel({
  style: {
    position: 'bottom-left',
    padding: '8px 15px'
  }
});

var legendTitle = ui.Label({
  value: 'Plastic Index (2025)',
  style: {fontWeight: 'bold', fontSize: '16px', margin: '0 0 4px 0', padding: '0'}
});
legend.add(legendTitle);

var makeRow = function(color, name) {
  var colorBox = ui.Label({
    style: {
      backgroundColor: color,
      padding: '8px',
      margin: '0 0 4px 0'
    }
  });
  var description = ui.Label({
    value: name,
    style: {margin: '0 0 4px 6px'}
  });
  return ui.Panel({
    widgets: [colorBox, description],
    layout: ui.Panel.Layout.Flow('horizontal')
  });
};

// Labels adjusted for Plastic Index typical ranges
var names = ['Water/Low', 'Low Probability', 'Medium Probability', 'High Probability (Potential Plastic)'];
var colors = ['#0000FF', '#00FFFF', '#FFFF00', '#FF0000'];

for (var i = 0; i < 4; i++) {
  legend.add(makeRow(colors[i], names[i]));
}

Map.add(legend);

// 7. Export the result
Export.image.toDrive({
  image: pi2025,
  description: 'Plastic_Index_2025',
  folder: 'EarthEngine_Exports',
  fileNamePrefix: 'PI_2025_Site',
  region: areaOfInterest,
  scale: 30,
  crs: 'EPSG:4326',
  fileFormat: 'GeoTIFF',
  formatOptions: {
    cloudOptimized: true
  }
});

Il problema e' che la risposta delle plastiche e' simile a quella della vegetazione (delle alghe nello specifico) per quanto riguardo l'infrarosso vicino


 

 Per cercare di discriminare si possono incrociare NDVI e PI

 

/**
 * Landsat 8 Plastic vs. Algae Discrimination Script
 * 2025 Median Composite
 */

var areaOfInterest =

    /* color: #98ff00 */
    /* shown: false */
    ee.Geometry.MultiPolygon(
        [[[[9.950883097809994, 43.88872579729536],
           [9.934403605622494, 43.48554689334167],
           [10.292832560700619, 43.51443524985007],
           [10.269486613434994, 43.53634133439625],
           [10.263993449372494, 43.62985044452412],
           [10.259873576325619, 43.7311534518937],
           [10.253007121247494, 43.76289953785138],
           [10.182969279450619, 43.910495519373605]]],
         [[[9.911900455707178, 43.92257421254923],
           [9.872075016254053, 43.45486023186587],
           [10.347233707660303, 43.44688454959106],
           [10.314274723285303, 43.50169612390543],
           [10.299168522113428, 43.51862782505474],
           [10.268956119769678, 43.55048637670663],
           [10.277195865863428, 43.565414278891666],
           [10.288182193988428, 43.57735393781841],
           [10.281315738910303, 43.60918145612981],
           [10.268956119769678, 43.656891171520144],
           [10.273075992816553, 43.70158462959755],
           [10.266209537738428, 43.73334638914901],
           [10.262089664691553, 43.78492333929173],
           [10.238743717425928, 43.83843695830995],
           [10.212651188129053, 43.89289227846965]]]]);




// 2. Cloud and Water Masking
var maskCloudsAndLand = function(image) {
  var qa = image.select('QA_PIXEL');
  // Cloud/Cirrus Mask
  var cloudMask = qa.bitwiseAnd(1 << 1).eq(0)
    .and(qa.bitwiseAnd(1 << 2).eq(0))
    .and(qa.bitwiseAnd(1 << 3).eq(0))
    .and(qa.bitwiseAnd(1 << 4).eq(0));
  
  // Optional: Simple Water Mask (using QA bits)
  // This helps focus only on marine/river surfaces
  var waterMask = qa.bitwiseAnd(1 << 7).neq(0); 
  
  return image.updateMask(cloudMask).updateMask(waterMask);
};

// 3. Discrimination Processing Function
var processDiscrimination = function(image) {
  // Scaling factors for Landsat 8 C2 L2
  var optical = image.select('SR_B.*').multiply(0.0000275).add(-0.2);
  
  // A. NDVI: Highlights Chlorophyll (Algae/Vegetation)
  var ndvi = optical.normalizedDifference(['SR_B5', 'SR_B4']).rename('NDVI');
  
  // B. Plastic Index (PI): Highlights high-reflectance floating solids
  var pi = optical.expression('B5 / (B5 + B6)', {
    'B5': optical.select('SR_B5'), // NIR
    'B6': optical.select('SR_B6')  // SWIR1
  }).rename('PI');

  // C. Discrimination Logic
  // Plastic: Moderate/High PI AND relatively low NDVI (compared to algae)
  // Algae: Very High NDVI AND lower PI (due to SWIR absorption by water content)
  var plasticMask = pi.gt(0.4).and(ndvi.lt(0.2)).rename('Plastic_Likely');
  var algaeMask = ndvi.gt(0.3).and(pi.lt(0.4)).rename('Algae_Likely');
  
  return image.addBands([ndvi, pi, plasticMask, algaeMask]);
};

// 4. Load Collection and Create Median
var collection = ee.ImageCollection("LANDSAT/LC08/C02/T1_L2")
  .filterBounds(areaOfInterest)
  .filterDate('2025-01-01', '2025-12-31')
  .map(maskCloudsAndLand)
  .map(processDiscrimination);

var finalMedian = collection.median().clip(areaOfInterest);

// 5. Visualization
Map.centerObject(areaOfInterest, 12);

// Natural Color for context
Map.addLayer(finalMedian, {bands:['SR_B4','SR_B3','SR_B2'], min:0, max:0.3}, 'Natural Color RGB');

// Algae Layer (Green)
Map.addLayer(finalMedian.select('Algae_Likely').selfMask(), {palette: ['#00FF00']}, 'Detected Algae');

// Plastic Layer (Red)
Map.addLayer(finalMedian.select('Plastic_Likely').selfMask(), {palette: ['#FF0000']}, 'Detected Plastic');

// 6. Legend for UI
var legend = ui.Panel({style: {position: 'bottom-right', padding: '8px 15px'}});
legend.add(ui.Label({value: 'Classification', style: {fontWeight: 'bold'}}));
var addLegendRow = function(color, text) {
  var row = ui.Panel({layout: ui.Panel.Layout.Flow('horizontal')});
  row.add(ui.Label({style: {backgroundColor: color, padding: '8px', margin: '0 0 4px 0'}}));
  row.add(ui.Label({value: text, style: {margin: '0 0 4px 6px'}}));
  legend.add(row);
};
addLegendRow('#00FF00', 'Organic / Algae (High NDVI)');
addLegendRow('#FF0000', 'Plastic / Debris (High PI, Low NDVI)');
Map.add(legend);

// 7. Export
Export.image.toDrive({
  image: finalMedian.select(['Plastic_Likely', 'Algae_Likely']),
  description: 'Plastic_vs_Algae_2025',
  folder: 'EarthEngine_Exports',
  region: areaOfInterest,
  scale: 30
}); 

 


Tutto molto lineare ed anche ragionevole che alla foce di un fiume come l'Arno vi sia un accumulo di microplastiche in mare. Ma se si legge l'articolo Monnanni et alii 2024 e' stimata una quantita' di fibre di 7.6 t/y  con 56,011 ± 16,411 particelle per litro rappresentate principalmente da dimensioni 60 μm

Considerando che studi su Sentinel 2 hanno posto il limite di detection della plastica del 25% di superficie del pixel e considerando che un pixel e' 1000 metri quadri (10x10 m) per una superficie di plastica di almeno 25 mq. Limite simile (30 mq su pixel di 30x30 m) viene indicato per Landsat 8. Non e' pensabile che di rilevare da satellite rilevare le concentrazioni indicate nell'articoli di Monnanni  

Volendo passare all'iperspettrale si vede una prima differenza (si precisa che il dato Enmap e'stato corretto con l'algoritmo di water correction..questo vuol dire che il dato scaricato da  EOWeb risulta mascherato sulla terraferma) Nel dato multispettrale di Sentinel 2 la riflettanza e' bassa ma apprezzabile nel dato iperspettrale si registra praticamente solo rumore

 
Sentinel 2

Enmap (correzione water)

 Questo comportamento e' dato dall'algoritmo MIP Modular Inversion Program di Enmap che di fatto forza a zero i valori di riflettanza sopra i 900 nm. Per avere un comportamento simile a Sentinel si deve quindi processare l'immagine Enmap su EOWeb con l'algoritmo di rimozione atmosferica terrestre (dove viene usato SICOR) anche se si sta lavorando in mare

 

Usando la correzione atmosferica per Land si ha come risulta uno spettro di questo tipo per punti in acqua

i punti a riflettanza 6.5 sono chiaramente errori e devono essere scartati. Modificando la scala verticale del grafico

si osserva che l'algoritmo Land di Enmap non taglia le lunghezze d'onda SWIR come l'algoritmo Water (anche se ovviamente avendo riflettanze cosi' basse il rumore strumentale e' molto significativo

 

Per replicare quanto fatto (unica immagine disponibili foce del Serchio) gli indici spettrali in multispettrale con i dati iperspettrali si usa l'approccio di derivata prima  

Red Edge Slope (Algae): (B_705nm - B_680nm) / 25 

Hydrocarbon Slope (Plastic): (B_1720nm - B_1730nm) / 15 

In Enmap con Band Math diventano

Algae = (Band054-Band050)/30  (706-679 nm) 
Hydro = (Band160-Band162)/21 (1717-1738 nm)

RGB

 

Hydro
 

Algae

 

con la regola si evidenziano i potenziali punti di presenza di plastica

if (Plastic_Derivative > 0.005 && Algae_Derivative < 0.002) then 1 else 0  

 








giovedì 12 febbraio 2026

Anomalie in NDVI

Questo script calcola l'anomalia dell'NDVI rispetto ad un valore medio

Unica accortezza.: non sono stati presi dati di Landsat 8 e 9 perche' si fondono male con Landsat 7






Map.centerObject(areaOfInterest, 15);
// 2. Funzione per NDVI scalato
var getNDVI = function(image) {
  var optical = image.multiply(0.0000275).add(-0.2);
  return optical.normalizedDifference(['SR_B4', 'SR_B3']).rename('NDVI')
    .copyProperties(image, ['system:time_start']);
};
// 3. Collezioni Landsat 4, 5, 7
var collection = ee.ImageCollection("LANDSAT/LT05/C02/T1_L2")
  .merge(ee.ImageCollection("LANDSAT/LE07/C02/T1_L2"))
  .filterBounds(areaOfInterest)
  .map(getNDVI);
// 4. CREAZIONE DELLE DUE EPOCHE (Baseline vs Recente)
// Epoca 1: Il passato (es. 1984-1994)
var past = collection.filterDate('1984-01-01', '1994-12-31').median();
// Epoca 2: Il presente (es. 2012-2022)
var present = collection.filterDate('2012-01-01', '2022-12-31').median();
// 5. CALCOLO DELLA MAGNITUDO DEL CAMBIAMENTO
// Differenza tra oggi e ieri
var change = present.subtract(past);
// 6. VISUALIZZAZIONE
var changeViz = {
  min: -0.3,
  max: 0.3,
  palette: ['#d73027', '#f4a582', '#f7f7f7', '#d9f0d3', '#1a9850']
};
Map.addLayer(past.clip(areaOfInterest), {min:0, max:0.8}, 'NDVI Passato (Anni 80/90)', false);
Map.addLayer(present.clip(areaOfInterest), {min:0, max:0.8}, 'NDVI Recente (Anni 2000/10)', false);
Map.addLayer(change.clip(areaOfInterest), changeViz, 'Cambiamento Netto (Magnitudo)');
// 7. Esportazione PNG del Cambiamento
var exportImg = change.visualize(changeViz);
print('Link PNG Cambiamento Storico:', exportImg.getThumbURL({
  dimensions: 1024,
  region: areaOfInterest,
  format: 'png'
}));


mercoledì 11 febbraio 2026

Mappa NDVI medio annuale con Google Earth Engine

Al contrario del precedente post https://debiaonoldcomputers.blogspot.com/2026/02/analisi-multitemporale-ndvi-con-google.html qui l'idea e' di creare una mappa di NDVI al posto della serie tempo per vedere anomalie spaziali

 


 

Landsat 8 

 // 1. Define the Study Area

var areaOfInterest = 
    /* color: #d63000 */
    /* displayProperties: [
      {
        "type": "rectangle"
      }
    ] */
    ee.Geometry.Polygon(
        [[[10.904865172222156, 43.01615135702198],
          [10.904865172222156, 43.00648615894694],
          [10.925807860210437, 43.00648615894694],
          [10.925807860210437, 43.01615135702198]]], null, false);

// 2. Pre-processing Function for Landsat 8
var processL8 = function(image) {
  var opticalBands = image.select('SR_B.').multiply(0.0000275).add(-0.2);
  var ndvi = opticalBands.normalizedDifference(['SR_B5', 'SR_B4']).rename('NDVI');
  return image.addBands(ndvi).select('NDVI');
};

// 3. Load 2025 Median NDVI
var ndvi2025 = ee.ImageCollection("LANDSAT/LC08/C02/T1_L2")
  .filterBounds(areaOfInterest)
  .filterDate('2025-01-01', '2025-12-31')
  .map(processL8)
  .median()
  .clip(areaOfInterest);

// 4. Visualization Parameters
var palette = [
  '#FFFFFF', '#CE7E45', '#FCD163', '#66A000', '#207401', '#056201', '#011301'
];
var ndviViz = {min: 0, max: 0.9, palette: palette};

// 5. Display the Map
Map.addLayer(ndvi2025, ndviViz, 'NDVI Median 2025');

// --- 6. ADDING THE LEGEND ---

// Create the legend panel
var legend = ui.Panel({
  style: {
    position: 'bottom-left',
    padding: '8px 15px'
  }
});

// Create legend title
var legendTitle = ui.Label({
  value: 'NDVI Index (2025)',
  style: {fontWeight: 'bold', fontSize: '16px', margin: '0 0 4px 0', padding: '0'}
});
legend.add(legendTitle);

// Helper function to create rows in the legend
var makeRow = function(color, name) {
  var colorBox = ui.Label({
    style: {
      backgroundColor: color,
      padding: '8px',
      margin: '0 0 4px 0'
    }
  });
  var description = ui.Label({
    value: name,
    style: {margin: '0 0 4px 6px'}
  });
  return ui.Panel({
    widgets: [colorBox, description],
    layout: ui.Panel.Layout.Flow('horizontal')
  });
};

// Define labels for the palette colors
var names = ['Low/No Veg (< 0.1)', 'Soil / Sparse', 'Moderate Veg', 'Dense Veg', 'Healthy Forest', 'Peak Vigour (> 0.8)'];
var colors = ['#FFFFFF', '#CE7E45', '#FCD163', '#66A000', '#207401', '#011301'];

// Add rows to legend
for (var i = 0; i < 6; i++) {
  legend.add(makeRow(colors[i], names[i]));
}

// Add legend to the map
Map.add(legend);

Export.image.toDrive({
  image: ndvi2025,            // The variable name of your NDVI map
  description: 'NDVI_2025_Tuscany', 
  folder: 'EarthEngine_Exports', // Name of the folder in your Google Drive
  fileNamePrefix: 'NDVI_2025_Site',
  region: areaOfInterest,     // Export only the area inside your polygon
  scale: 30,                  // Landsat resolution is 30 meters
  crs: 'EPSG:4326',           // Standard Coordinate Reference System (WGS84)
  fileFormat: 'GeoTIFF',
  formatOptions: {
    cloudOptimized: true      // Makes the file easier to open in QGIS/ArcGIS
  }
});

 

Landsat 7

// 1. Define the Study Area
var areaOfInterest = 
    /* color: #d63000 */
    /* displayProperties: [
      {
        "type": "rectangle"
      }
    ] */
    ee.Geometry.Polygon(
        [[[10.904865172222156, 43.01615135702198],
          [10.904865172222156, 43.00648615894694],
          [10.925807860210437, 43.00648615894694],
          [10.925807860210437, 43.01615135702198]]], null, false);
// 2. Pre-processing Function for Landsat 8
var processL8 = function(image) {
  var opticalBands = image.select('SR_B.').multiply(0.0000275).add(-0.2);
  var ndvi = opticalBands.normalizedDifference(['SR_B5', 'SR_B4']).rename('NDVI');
  return image.addBands(ndvi).select('NDVI');
};

// 3. Load 2025 Median NDVI
var l7_2005 = ee.ImageCollection("LANDSAT/LE07/C02/T1_L2")
  .filterBounds(areaOfInterest)
  .filterDate('2005-01-01', '2005-12-31')
  .map(function(image) {
    var opticalBands = image.select('SR_B.').multiply(0.0000275).add(-0.2);
    // Landsat 7 NDVI: (B4 - B3) / (B4 + B3)
    var ndvi = opticalBands.normalizedDifference(['SR_B4', 'SR_B3']).rename('NDVI');
    return image.addBands(ndvi).select('NDVI');
  })
  .median() // Create a clean composite for the year
  .clip(areaOfInterest);

// 4. Visualization Parameters
var palette = [
  '#FFFFFF', '#CE7E45', '#FCD163', '#66A000', '#207401', '#056201', '#011301'
];
var ndviViz = {min: 0, max: 0.9, palette: palette};

// 5. Display the Map
Map.addLayer(l7_2005, ndviViz, 'NDVI Median 2005');

// --- 6. ADDING THE LEGEND ---

// Create the legend panel
var legend = ui.Panel({
  style: {
    position: 'bottom-left',
    padding: '8px 15px'
  }
});

// Create legend title
var legendTitle = ui.Label({
  value: 'NDVI Index (2025)',
  style: {fontWeight: 'bold', fontSize: '16px', margin: '0 0 4px 0', padding: '0'}
});
legend.add(legendTitle);

// Helper function to create rows in the legend
var makeRow = function(color, name) {
  var colorBox = ui.Label({
    style: {
      backgroundColor: color,
      padding: '8px',
      margin: '0 0 4px 0'
    }
  });
  var description = ui.Label({
    value: name,
    style: {margin: '0 0 4px 6px'}
  });
  return ui.Panel({
    widgets: [colorBox, description],
    layout: ui.Panel.Layout.Flow('horizontal')
  });
};

// Define labels for the palette colors
var names = ['Low/No Veg (< 0.1)', 'Soil / Sparse', 'Moderate Veg', 'Dense Veg', 'Healthy Forest', 'Peak Vigour (> 0.8)'];
var colors = ['#FFFFFF', '#CE7E45', '#FCD163', '#66A000', '#207401', '#011301'];

// Add rows to legend
for (var i = 0; i < 6; i++) {
  legend.add(makeRow(colors[i], names[i]));
}

// Add legend to the map
Map.add(legend);

Export.image.toDrive({
  image: l7_2005,                // Updated to the 2005 variable
  description: 'NDVI_L7_2005_Tuscany', 
  folder: 'EarthEngine_Exports', 
  fileNamePrefix: 'NDVI_Landsat7_2005',
  region: areaOfInterest,     
  scale: 30,                  
  crs: 'EPSG:4326',           
  fileFormat: 'GeoTIFF',
  formatOptions: {
    cloudOptimized: true      
  }
});


 

 

Analisi multitemporale NDVI con Google Earth Engine

Qualche esperimento con Google Earth Engine per vedere come si modifica nel tempo NDVI (arco di decenni fornito da Landsat)

 


 

// 1. Coordinate e Geometria
//var lon = 10.913507726271437;
//var lat = 43.01194709215584;

var lon = 10.926468160041452;
var lat = 43.01919548868449;


var poi = ee.Geometry.Point([lon, lat]);

// 2. Funzione di pre-elaborazione (Mask Cloud e Scaling)
// Valida per Landsat 5 e Landsat 7 (Collection 2 Level 2)
function preprocessLandsat(image) {
  var qa = image.select('QA_PIXEL');
  
  // Maschera per nuvole, ombre e cirri
  var mask = qa.bitwiseAnd(1 << 1).eq(0)
    .and(qa.bitwiseAnd(1 << 2).eq(0))
    .and(qa.bitwiseAnd(1 << 3).eq(0))
    .and(qa.bitwiseAnd(1 << 4).eq(0));

  // Fattori di scala per Collection 2
  var opticalBands = image.select('SR_B.').multiply(0.0000275).add(-0.2);
  
  // Calcolo NDVI (NIR è B4, RED è B3 per entrambi i sensori L5 e L7)
  var ndvi = opticalBands.normalizedDifference(['SR_B4', 'SR_B3']).rename('NDVI');
  
  return image.addBands(ndvi).updateMask(mask);
}

// 3. Caricamento Collezione LANDSAT 5 (1985 - 2010)
var l5Col = ee.ImageCollection("LANDSAT/LT05/C02/T1_L2")
  .filterBounds(poi)
  .filterDate('1985-01-01', '2010-01-01')
  .map(preprocessLandsat);

// 4. Caricamento Collezione LANDSAT 7 (2010 - 2024)
var l7Col = ee.ImageCollection("LANDSAT/LE07/C02/T1_L2")
  .filterBounds(poi)
  .filterDate('2010-01-01', '2024-01-01')
  .map(preprocessLandsat);

// 5. Unione delle due collezioni
var mergedCol = l5Col.merge(l7Col);

// 6. Creazione del Grafico Temporale
var chart = ui.Chart.image.series({
  imageCollection: mergedCol.select('NDVI'),
  region: poi,
  reducer: ee.Reducer.mean(),
  scale: 30,
  xProperty: 'system:time_start'
})
.setOptions({
  title: 'Serie Storica NDVI: Landsat 5 e Landsat 7 (1985-2024)',
  vAxis: {title: 'NDVI', viewWindow: {min: -0.1, max: 1}},
  hAxis: {title: 'Anno', format: 'YYYY'},
  lineWidth: 1,
  pointSize: 2,
  trendlines: {0: {color: 'red', lineWidth: 1, opacity: 0.7}}, // Linea di tendenza globale
  series: {
    0: {color: '228B22'} // Colore verde foresta
  }
});

// 7. Visualizzazione
print(chart);
Map.centerObject(poi, 13);
Map.addLayer(poi, {color: 'red'}, 'Sito di analisi'); 

 

 

uno script piu' evoluto si puo'trovare qui. L'interesse e' che non viene calcolato un singolo punto ma il valore medio di un'area che risulta sicuramente piu' significativo
 

modificando per l'area di interesse

 

// 1. Definizione dell'Area di Studio (Rettangolo)
var areaOfInterest = ee.Geometry.Polygon([
  [[10.910535746643099, 43.01138938313477],
   [10.91448395831302, 43.01138938313477],
   [10.91448395831302, 43.00825125524326],
   [10.910535746643099, 43.00825125524326],
   [10.910535746643099, 43.01138938313477]]
]);

Map.centerObject(areaOfInterest, 15);
Map.addLayer(areaOfInterest, {color: 'd63000'}, 'Area di Studio');

// 2. Funzione Processamento con Soglia NDVI > 0.55
var processLandsat = function(image, nir, red) {
  var opticalBands = image.select('SR_B.').multiply(0.0000275).add(-0.2);
  var ndvi = opticalBands.normalizedDifference([nir, red]).rename('NDVI');
  
  // FILTRO: Manteniamo solo i pixel dove NDVI è maggiore di 0.55
  var maskThreshold = ndvi.gt(0.55);
  
  return image.addBands(ndvi).select('NDVI')
    .updateMask(maskThreshold) // Rimuove i valori bassi
    .copyProperties(image, ['system:time_start']);
};

// 3. Caricamento Collezioni
var L5 = ee.ImageCollection("LANDSAT/LT05/C02/T1_L2").filterBounds(areaOfInterest)
  .map(function(img) { return processLandsat(img, 'SR_B4', 'SR_B3'); });

var L7 = ee.ImageCollection("LANDSAT/LE07/C02/T1_L2").filterBounds(areaOfInterest)
  .map(function(img) { return processLandsat(img, 'SR_B4', 'SR_B3'); });

var L8 = ee.ImageCollection("LANDSAT/LC08/C02/T1_L2").filterBounds(areaOfInterest)
  .map(function(img) { return processLandsat(img, 'SR_B5', 'SR_B4'); });

var L9 = ee.ImageCollection("LANDSAT/LC09/C02/T1_L2").filterBounds(areaOfInterest)
  .map(function(img) { return processLandsat(img, 'SR_B5', 'SR_B4'); });

var mergedNDVI = L5.merge(L7).merge(L8).merge(L9);

// 4. Composizione Mensile (Massimo Valore)
var years = ee.List.sequence(1984, 2024);
var months = ee.List.sequence(1, 12);

var monthlyNDVI = ee.ImageCollection.fromImages(
  years.map(function(y) {
    return months.map(function(m) {
      var d = ee.Date.fromYMD(y, m, 1);
      return mergedNDVI
        .filter(ee.Filter.calendarRange(y, y, 'year'))
        .filter(ee.Filter.calendarRange(m, m, 'month'))
        .max() // Seleziona il valore più alto (più verde) del mese
        .set('system:time_start', d.millis());
    });
  }).flatten()
);

// Rimuoviamo i periodi senza dati (es. inverno o periodi sotto soglia)
var monthlyNDVI_Clean = monthlyNDVI.filter(ee.Filter.listContains('system:band_names', 'NDVI'));

// 5. Grafico
var chart = ui.Chart.image.series({
  imageCollection: monthlyNDVI_Clean,
  region: areaOfInterest,
  reducer: ee.Reducer.mean(),
  scale: 30,
  xProperty: 'system:time_start'
})
.setOptions({
  title: 'NDVI Filtrato (>0.55) - Peak Vegetation Trend',
  vAxis: {title: 'NDVI', viewWindow: {min: 0.55, max: 0.9}},
  hAxis: {title: 'Anno', format: 'YYYY'},
  trendlines: {0: {color: 'red', lineWidth: 1}},
  series: {0: {color: '228B22', pointSize: 2, lineWidth: 1}}
});

print(chart); 

 


 


in generale sembra che NDVI sia migliorato a partire dal 2013

venerdì 21 aprile 2023

Earth Engine Sentinel 5P time series NO2

 Due script per scaricare serie tempo dei dati Sentinel 5P da Google Earth Engine dato un punto geografico




Tutti i dati

================================================================

var point = ee.Geometry.Point([11.097849, 43.793234]);
var sentinel = ee.ImageCollection('COPERNICUS/S5P/NRTI/L3_NO2');
var sentinelLST = sentinel.filterBounds(point)
.filterDate('2019-01-01', '2022-12-31')
.select('NO2_column_number_density');

sentinelLST = sentinelLST.map(function(img){
var date = img.get('system:time_start');
return img.multiply(100000).set('system_time_start', date);
});

var createTS = function(img){
var date = img.get('system_time_start');
var value = img.reduceRegion(ee.Reducer.mean(), point).get('NO2_column_number_density');
var ft = ee.Feature(null, {'system:time_start': date,
'date': ee.Date(date).format('Y/M/d'),
'value': value});
return ft;
};

var TS = sentinelLST.map(createTS);

var graph = ui.Chart.feature.byFeature(TS, 'system:time_start', 'value');

print(graph.setChartType("Sentinel 5P")
.setOptions({vAxis: {title: 'NO2'},
hAxis: {title: 'Date'}}));


Export.table.toDrive({collection: TS, selectors: 'date, value'});

================================================================

Medie Giornaliere

================================================================

var collection = ee.ImageCollection('COPERNICUS/S5P/OFFL/L3_NO2')
  .select('tropospheric_NO2_column_number_density')
var daily_data = ee.ImageCollection(ee.List.sequence(2019,2019).map(function(year){
  var date1 = ee.Date.fromYMD(year,1,1)
  var date2 = date1.advance(1,'year')
  //Calculate the number of days per year
  var doy = date2.difference(date1,'day')
  var doyList = ee.List.sequence(1,doy)
  //Daily image mean synthesis using doy
  var day_imgs = doyList.map(function(doy){
    doy = ee.Number(doy)
    var temp_date = date1.advance(doy.subtract(1),"day")
    var temp_img = collection.filterDate(temp_date,temp_date.advance(1,'day'))
    return temp_img.mean().set("system:time_start",temp_date.millis())
  })
  return day_imgs
}).flatten())
Map.addLayer(daily_data)
var chart = ui.Chart.image.series({
imageCollection:daily_data,
region:roi,
reducer:ee.Reducer.mean(),
scale:1113.2,
// xProperty:,
})
print(chart)

================================================================


Medie Mensili

================================================================


var point = ee.Geometry.Point([11.097849, 43.793234]);
var dataset = ee.ImageCollection('COPERNICUS/S5P/NRTI/L3_NO2')
          .filterBounds(point)
          .filterDate('2019-01-01', '2022-12-31')
          .select('NO2_column_number_density');
var months = ee.List.sequence(1, 12);
var start_year = 2019;
var start_date = '2019-01-01';
var end_year = 2022;
var end_date = '2022-12-31';
var years = ee.List.sequence( start_year, end_year);

var byMonthYear =  ee.FeatureCollection(
  years.map(function (y) {
    return months.map(function(m){
          var w = dataset.filter(ee.Filter.calendarRange(y, y, 'year'))
                    .filter(ee.Filter.calendarRange(m, m, 'month'))
                    .mean();
      var pointMean = w.reduceRegion({reducer:ee.Reducer.first(), geometry:point,scale:1000});  
      return ee.Feature(null).set("value",pointMean.get("NO2_column_number_density")).set("year",y).set("month",m);
    })
  }).flatten()
);

print("feature collection",byMonthYear);

Export.table.toDrive({collection:byMonthYear,description:"O3_signa"})

================================================================


mercoledì 8 marzo 2023

Formaldeide e metano da Sentinel 5 con Google Earth Engine

 Usare Google Earth Engine per Sentinel 5 e' comodo perche' i pixel sono stati ricalcolati in modo omogeneo (nei file netcdf originali i pixel hanno dimensioni differenti a secondo dell'orbita)

I valori di CH4 sono espressi in ppm ed il range di valore e' estremamente stretto (da 1860 a 1890 ppm nell'immagine ) con la dimensione del pixel di 1113 m. I dati sono disponibili sono per le aree di fondovalle




I dati di formaldeide sono espressi in mol/m2 ed il range di valori varia tra 0.00007 e 0.00014 con pixel di 1113 m. 




var palettes = require('users/gena/packages:palettes');
  
var palette_r = palettes.colorbrewer.Accent[5];
var collection = ee.ImageCollection('COPERNICUS/S5P/NRTI/L3_HCHO')
  .select('tropospheric_HCHO_column_number_density')
  .filterDate('2019-01-01', '2019-12-31');
var median = collection.reduce(ee.Reducer.mean());
var band_viz = {
  min: 0.00007,
  max: 0.00014,
  palette: ['black', 'blue', 'purple', 'cyan', 'green', 'yellow', 'red']
};
Map.addLayer(collection.mean(), band_viz, 'S5P HCHO');
Map.setCenter(11.295553516712024, 43.976202446636606,9);
Map.style().set('cursor', 'crosshair');
var header = ui.Label('HCHO', {fontWeight: 'bold', fontSize: '18px'});
var toolPanel = ui.Panel([header], 'flow', {width: '400px'});
Map.onClick(function(coords) {
  // Create or update the location label (the second widget in the panel)
  var location = 'lon: ' + coords.lon.toFixed(4) + ' ' +
                 'lat: ' + coords.lat.toFixed(4);
  var click_point = ee.Geometry.Point(coords.lon, coords.lat);
  var CH4Value = median.reduceRegion(ee.Reducer.first(), click_point, 90).evaluate(function(val){
    console.log(val);
    var CH4Text = 'CH4: ' + val;
    toolPanel.widgets().set(2, ui.Label(CH4Text));
  });
  
  toolPanel.widgets().set(1, ui.Label(location));
  // Add a red dot to the map where the user clicked.
  Map.layers().set(1, ui.Map.Layer(click_point, {color: 'FF0000'}));
});
// Add the panel to the ui.root.
ui.root.add(toolPanel);

giovedì 17 novembre 2022

Geotiff da assets in EarthEngine

In Earth Engine e' possibile visualizzare dei Geotiff caricandoli in assets 


I geotiff devono essere puri e non tiff con allegato il world file in formato tfw.

Per la conversione da tfw a geotiff si puo' usare Gdal Translate includendo anche il sistema di riferimento

(quello che segue e' un esempio di conversione di un foglio CARG Carta geologica regionale Toscana)

gdal_translate -a_srs EPSG:3003 -of GTiff 264020.tif 264020_.tif

Le immagini che sono in assets non sono immediatamente leggibili dalla versione app del progetto.

Devono essere esplicitamente impostati i permessi di lettura nelle proprieta' dell'asset


Per visualizzare file prendendolo dagli assets si usa la seguente sintassi

var geologia = ee.Image('projects/ee-lucainnoc/assets/264020');

Map.addLayer(geologia, {},'Geologia',false,0.5);


mercoledì 16 novembre 2022

Soil Moisture Sentinel 1 Google Earth Engine

 Visto che le immagini radar presenti su Google EarthEngine sono gia' radiometricamente e geometricamente corrette ho voluto provare a replicare su questa piattaforma quanto visto nel precedente post



la demo funzionante e' all'indirizzo sotto riportato

https://lucainnoc.users.earthengine.app/view/soil-moisture-sentinel-1

Questa e' la spvrapposizione tra la geologia e l'algoritmo dell'area del precedente post (Casole, Vicchio di Mugello). 

La mappa e' la differenza di umidita' tra il periodo primaverile ed estivo dell'anno 2021 con il colore blu che indica massima variazione negativa e il colore rosso massima variazione positiva


function maskS2clouds(image) {
  var qa = image.select('QA60');
  // Bits 10 and 11 are clouds and cirrus, respectively.
  var cloudBitMask = 1 << 10;
  var cirrusBitMask = 1 << 11;
  // Both flags should be set to zero, indicating clear conditions.
  var mask = qa.bitwiseAnd(cloudBitMask).eq(0)
      .and(qa.bitwiseAnd(cirrusBitMask).eq(0));
  return image.updateMask(mask).divide(10000);
}

var imgVV = ee.ImageCollection('COPERNICUS/S1_GRD')
        .filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VV'))
        .filter(ee.Filter.eq('instrumentMode', 'IW'))
        .select('VV')
        .map(function(image) {
          var edge = image.lt(-30.0);
          var maskedImage = image.mask().and(edge.not());
          return image.updateMask(maskedImage);
        });
//seleziona solo ASCENDENTE
var asc = imgVV.filter(ee.Filter.eq('orbitProperties_pass', 'ASCENDING'));

//seleziona il periodo primaverile ed estivo
var spring = ee.Filter.date('2021-03-01', '2021-04-20');
var summer = ee.Filter.date('2021-06-11', '2021-08-31');
//seleziona il periodo dal gennaio 2019 al dicembre 2021
//per ottenere i valori massimi e minimi
var full = ee.Filter.date('2019-01-01', '2021-12-31');
var sar_dataset_min = ee.Image.cat(asc.filter(full).min());
var sar_dataset_max = ee.Image.cat(asc.filter(full).max());
// calcola la variazione massima nel periodo di interesse 2019-2021
var sensitivity = sar_dataset_max.subtract(sar_dataset_min);

var primavera = ee.Image.cat(asc.filter(spring).mean());
var estate = ee.Image.cat(asc.filter(summer).mean());
var Mv_primavera = (primavera.subtract(sar_dataset_min)).divide(sensitivity);
var Mv_estate = (estate.subtract(sar_dataset_min)).divide(sensitivity);
var delta = Mv_estate.subtract(Mv_primavera);
//var Mv_estate = (estate - sar_dataset_min)/(sensitivity);
//print(Mv_primavera);
//estre i truecolor ottici per riferimento
var rgb_spring = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
                  .filterDate('2018-03-01', '2018-04-20')
                  // Pre-filter to get less cloudy granules.
                  .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE',20))
                  .map(maskS2clouds);
var visualization = {
  min: 0.0,
  max: 0.3,
  bands: ['B4', 'B3', 'B2'],
};
var rgb_summer = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
                  .filterDate('2018-06-11', '2018-08-31')
                  // Pre-filter to get less cloudy granules.
                  .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE',20))
                  .map(maskS2clouds);
var visualization = {
  min: 0.0,
  max: 0.3,
  bands: ['B4', 'B3', 'B2'],
};

Map.setCenter(11.489351719442514,43.95792446517467, 14);
//Map.addLayer(primavera, {min: -25, max: 5}, 'Spring', true);
//Map.addLayer(estate, {min: -25, max: 5}, 'Summer', true);
Map.addLayer(rgb_spring.mean(), visualization, 'RGB Spring');
Map.addLayer(rgb_summer.mean(), visualization, 'RGB Summer',false);
var SensitivityBandVis = {
  min: 0,
  max: 50,
  palette: ['blue', 'yellow', 'green']
};
//Map.addLayer(rgb_spring.mean(), visualization, 'RGB Spring');
//Map.addLayer(sensitivity, SensitivityBandVis, 'Sensitivity');

var MvBandVis = {
  min: 0,
  max: 1,
  palette: ['blue', 'yellow', 'green']
};

var DeltaBandVis = {
  min: -1,
  max: 1,
  palette: ['blue', 'yellow', 'green']
};
Map.addLayer(Mv_primavera, MvBandVis, 'Mv Spring',false);
Map.addLayer(Mv_estate, MvBandVis, 'Mv Summer',false);
Map.addLayer(delta, DeltaBandVis, 'Mv Delta',true,0.5);

martedì 23 agosto 2022

Copertura nevosa con Google Earth Engine ed ERA5

Questo dato e' stato ricercato perche' in un settore dell'Appennino ToscoEmiliano qualche anno fa si sono riattivate numerose frane. Il sospetto e' che fossero cadute nevicate tardive che si sono poi sciolte rapidamente con l'arrivo del caldo invernale innescando i movimenti di versante

Sull'area non sono presenti nivometri per cui non vi sono dati di verita' a terra. Le immagini Landsat ovviamente nel periodo invernale sono spesso nuvolose e di poco utilizzo

Un primo metodo e' quello di utilizzare i dati di ERA5

Questa analisi si puo' fare interrogando direttamente le API di Copernicus senza passare da Google Earth Engine

===================================================

var geometry = /* color: #d63000 */ee.Geometry.MultiPoint(
        [[10.2014189280409, 44.47706314233407],
         [10.389914585837579, 44.524348579528535],
         [10.228782890268272, 44.53074818785566],
         [10.075498095653819, 44.458900035551736],
         [10.100715972012257, 44.51255997214677]]);
/***** End of imports. If edited, may not auto-convert in the playground. *****/

var point = ee.Geometry.Point([10.2014189280409, 44.47706314233407]);
var snow_depth = ee.ImageCollection('ECMWF/ERA5_LAND/MONTHLY')
            .select('snow_depth')
            .filterBounds(point)
            .filter(ee.Filter.calendarRange(2000,2020,'year'))
            .map(function(image){return image.clip(point)}) ;
      

// plot full time series
print(
  ui.Chart.image.series({
    imageCollection: snow_depth,
    region: point,
    scale: 1000
  }).setOptions({title: 'Snow Monthly ERA5'})
);

===================================================

Altrimenti si puo' usare l'indice NDIS_Snow_Cover di Modis che ha un dettagli temporale molto maggiore


===================================================

var geometry = /* color: #d63000 */ee.Geometry.MultiPoint(
        [[10.2014189280409, 44.47706314233407],
         [10.389914585837579, 44.524348579528535],
         [10.228782890268272, 44.53074818785566],
         [10.075498095653819, 44.458900035551736],
         [10.100715972012257, 44.51255997214677]]);
/***** End of imports. If edited, may not auto-convert in the playground. *****/


var startDate = ee.Date('2010-01-01'); // set start time for analysis
var endDate = ee.Date('2022-01-01'); // set end time for analysis
var nDays = ee.Number(endDate.difference(startDate,'day')).round();

var point = ee.Geometry.Point([10.04337, 45.71931]);

var chirps = ee.ImageCollection('MODIS/006/MOD10A1')
            .select('NDSI_Snow_Cover')
            .filterBounds(point)
            .filterDate(startDate, endDate)
            .map(function(image){return image.clip(point)}) ;

      



var byday = ee.ImageCollection(
  // map over each day
  ee.List.sequence(0,nDays).map(function (n) {
    var ini = startDate.advance(n,'day');
    // advance just one day
    var end = ini.advance(1,'day');
    return chirps.filterDate(ini,end)
                .select(0).mean()
                .set('system:time_start', ini);
}));


//print(byday);
Map.setCenter(10.04582, 45.71337, 11);

//Map.addLayer(ee.Image(byday.mean()),{min: 0, max: 50},'Precipitazioni');

// plot full time series
print(
  ui.Chart.image.series({
    imageCollection: byday,
    region: point,
    scale: 1000
  }).setOptions({title: 'MODIS NDSI'})
  );

Telerilevamento plastiche in mare

Bibliografia Biermann, L., Clewley, D., Martinez-Vicente, V. et al. Finding Plastic Patches in Coastal Waters using Optical Satellite Data. ...