Google Earth Engine (GEE) Code to Calculate Moment Distance Index Normalized (MDIN) Using Sentinel-2 or LandSat Images

MDIN stands for Moment Distance Index Normalized, originally developed by Salas and Henebry (2013) and improved by Salas and Subburayalu (2019). We developed MDIN to improve the spatial-spectral classification of hyperspectral data for agricultural management systems. MDIN analyzes the full spectral curve across different wavelengths.

How it Works:

(1) Distance Calculation: The index calculates the distance of each band’s reflectance from two “pivots” within the spectral curve: the left pivot and the right pivot. (2) Normalization: These distances are normalized to create a value between -1 and 1.

    Below is the simplified code for calculating MDIN using a Sentinel-2 image on the Google Earth Engine (GEE) platform.

    //Get the boudary of your study area.  
    var ohio = ee.FeatureCollection('TIGER/2018/States')
      .filter(ee.Filter.eq('NAME', 'Ohio'));
    
    // Function to calculate MDIN for Sentinel-2 imagery.  
    // If you want to use LandSat bands, make sure to change/edit the bands below.  
    var calculateMDIN = function(image) {
      // Define wavelengths for Sentinel-2 bands (in nanometers)
      var wavelengths = {
        'B2': 490,   // Blue (LP - Left Pivot)
        'B3': 560,   // Green
        'B4': 665,   // Red
        'B5': 705,   // Red Edge 1
        'B6': 740,   // Red Edge 2
        'B7': 783,   // Red Edge 3
        'B8': 842,   // NIR
        'B8A': 865,  // Red Edge 4
        'B11': 1610, // SWIR 1
        'B12': 2190  // SWIR 2 (RP - Right Pivot)
      };
      
      // Get all bands for calculation
      var allBands = ['B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B8A', 'B11', 'B12'];
    
      // Calculate MDLP (Left Pivot to Right Pivot)
      // If using LandSat, change Sentinel-2 [B2] with the first band of LandSat.
      var mdlpSum = ee.Image(0);
      allBands.forEach(function(band) {
        var reflectance = image.select(band).subtract(image.select(allBands).reduce(ee.Reducer.mean())); // Centralize reflectance
        var wavelengthPosition = ee.Number(wavelengths[band]);
        var leftPivotDiff = wavelengthPosition.subtract(wavelengths['B2']); // Distance from LP
        
        // Hypotenuse: sqrt(ρ² + (i-λLP)²)
        var distanceLP = reflectance.pow(2).add(leftPivotDiff.pow(2)).sqrt();
        mdlpSum = mdlpSum.add(distanceLP);
      });
    
      // Calculate MDRP (Right Pivot to Left Pivot)
      // If using LandSat, change Sentinel-2 [B12] with the last band of LandSat.
      var mdrpSum = ee.Image(0);
      allBands.forEach(function(band) {
        var reflectance = image.select(band).subtract(image.select(allBands).reduce(ee.Reducer.mean())); // Centralize reflectance
        var wavelengthPosition = ee.Number(wavelengths[band]);
        var rightPivotDiff = ee.Number(wavelengths['B12']).subtract(wavelengthPosition); // Distance from RP
        
        // Hypotenuse: sqrt(ρ² + (λRP-i)²)
        var distanceRP = reflectance.pow(2).add(rightPivotDiff.pow(2)).sqrt();
        mdrpSum = mdrpSum.add(distanceRP);
      });
    
      // Normalize MDLP and MDRP
      var normalizedMDLP = mdlpSum.divide(mdlpSum.add(mdrpSum));
      var normalizedMDRP = mdrpSum.divide(mdlpSum.add(mdrpSum));
      
      // Calculate MDIN = (MDRP - MDLP)/(MDRP + MDLP)
      var mdin = normalizedMDRP.subtract(normalizedMDLP).rename('MDIN');
    
      return image.addBands(mdin);
    };
    
    
    // Get Sentinel-2 collection for summer 2023. Change the dates depending on your study dates. 
    var s2Collection = ee.ImageCollection('COPERNICUS/S2_SR')
      .filterDate('2023-05-01', '2023-06-30')
      .filterBounds(ohio.geometry())
      .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 10));
    
    // Calculate MDIN for each image
    var mdinCollection = s2Collection.map(calculateMDIN);
    
    // Calculate the mean MDIN across all images
    var meanMDIN = mdinCollection.select('MDIN').mean();
    
    // Clip to Ohio boundary
    var mdinOhio = meanMDIN.clip(ohio);
    
    // Calculate MDIN statistics for visualization
    var mdinStats = mdinOhio.reduceRegion({
      reducer: ee.Reducer.max(),
      geometry: ohio.geometry(),
      scale: 1000,
      maxPixels: 1e9
    });
    
    // Get the maximum MDIN value
    var maxMDIN = ee.Number(mdinStats.get('MDIN'));
    print('Maximum MDIN:', maxMDIN);
    
    // Create a palette with 20 colors
    var palette = [
      '#0000FF', '#0033FF', '#0066FF', '#0099FF', '#00CCFF',
      '#00FFFF', '#33FFCC', '#66FF99', '#99FF66', '#CCFF33',
      '#FFFF00', '#FFCC00', '#FF9900', '#FF6600', '#FF3300',
      '#FF0000', '#CC0033', '#990066', '#660099', '#3300CC'
    ];
    
    // Visualization parameters with 20 categories
    var mdinVis = {
      min: 0,
      max: maxMDIN.getInfo(),
      palette: palette
    };
    
    // Center map on Ohio
    Map.centerObject(ohio, 7);
    
    // Add layers
    Map.addLayer(mdinOhio, mdinVis, 'Average Summer MDIN Ohio');
    Map.addLayer(ohio.style({color: 'black', fillColor: '00000000'}), {}, 'Ohio boundary');
    
    // Add a legend for the 20 categories
    var createCategoricalLegend = function(palette, min, max, categories) {
      var legend = ui.Panel({
        style: {
          position: 'bottom-right',
          padding: '8px 15px'
        }
      });
    
      // Legend title
      var legendTitle = ui.Label({
        value: 'MDIN Categories',
        style: {
          fontWeight: 'bold',
          fontSize: '16px',
          margin: '0 0 4px 0',
          padding: '0'
        }
      });
    
      legend.add(legendTitle);
    
      // Create rows for each category
      var step = (max - min) / categories;
      for (var i = 0; i < categories; i++) {
        var value = (min + i * step).toFixed(2) + ' to ' + (min + (i + 1) * step).toFixed(2);
        var colorBox = ui.Label({
          style: {
            backgroundColor: palette[i],
            padding: '8px',
            margin: '0 0 4px 0'
          }
        });
        var description = ui.Label({
          value: value,
          style: {margin: '0 0 4px 6px'}
        });
        legend.add(ui.Panel([colorBox, description], ui.Panel.Layout.Flow('horizontal')));
      }
    
      return legend;
    };
    
    // Add the categorical legend to the map
    var legend = createCategoricalLegend(palette, 0, maxMDIN.getInfo(), 20);
    Map.add(legend);
    
    // Add a chart showing MDIN values over time
    var chart = ui.Chart.image.series({
      imageCollection: mdinCollection.select('MDIN'),
      region: ohio.geometry(),
      reducer: ee.Reducer.mean(),
      scale: 1000
    }).setOptions({
      title: 'Average MDIN Values Over Summer 2023',
      vAxis: {title: 'MDIN'},
      hAxis: {title: 'Date'},
      trendlines: {0: {}}
    });
    
    print(chart);

    If you use the code above or apply MDIN, kindly use this citation:

    Salas, EAL and Subburayalu, SK (2019). Modified shape index for object-based random forest image classification of agricultural systems using airborne hyperspectral datasets. PLoS ONE 14(3): e0213356. https://doi.org/10.1371/journal.pone.0213356

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    7 + 6 =

    This site uses Akismet to reduce spam. Learn how your comment data is processed.