使用GEE以及LandSat8植被指数NDVI计算

进入GEE

右上角dataset打开

页面往下拉找到Landsat系列数据

选择地表反射率的L2层级产品,这个产品是做了辐射定标,大气校正,以及正射校正的产品

点击L2进入如下界面

下拉找到代码编辑器,点击

转到如下界面

可以新建一个项目或者是使用一个创建好的项目,之后的结果会存放再对应的项目之下,第一次使用需要登录谷歌账号绑定google cloud,可以创建项目相关文件夹

绑定完成之后输入js代码,选择研究区域,以及时间,在时间范围内筛选云量较少,有效区域范围95%以上的影像,建议不要使用镶嵌影像,因为在内蒙等区域植被生长周期不同,镶嵌尽管相差一两个月也可能导致植被的生长周期出现巨大差异,将代码复制点击Run运行。

bash 复制代码
```javascript
/************************************
 * 丰镇周边15km Landsat8 L2 两期植被分析
 *
 * 方法:
 * 1. ROI缩小为丰镇中心周边15km
 * 2. 每期只选择一景Landsat8 L2影像,不镶嵌
 * 3. 后期保持2024年7-8月窗口
 * 4. 前期放宽到2020年6-9月窗口,重新筛选有效影像
 * 5. 加入VALID_RATIO,保证云掩膜后ROI内有效像元比例足够高
 *
 * 导出连续值:
 * 1. 前期L2多波段
 * 2. 后期L2多波段
 * 3. 前期NDVI
 * 4. 后期NDVI
 * 5. 前期FVC
 * 6. 后期FVC
 * 7. NDVI差异:后期 - 前期
 * 8. FVC差异:后期 - 前期
 *
 * 地图显示:
 * NDVI、FVC、NDVI差异、FVC差异使用5级可视化
 ************************************/


// ===============================
// 1. 研究区:丰镇市中心周边15km
// ===============================

var fengzhenPoint = ee.Geometry.Point([113.169, 40.440]);

// 周边15km范围,外接矩形,便于导出
var roi = fengzhenPoint.buffer(15000).bounds();

Map.centerObject(roi, 10);


// ===============================
// 2. 白色背景图层:用于隐藏GEE底图
// ===============================

// 如果你想看Google底图,就在Layers里取消勾选这个图层
var whiteBackground = ee.Image.constant(1).visualize({
  min: 0,
  max: 1,
  palette: ['ffffff']
});

Map.addLayer(
  whiteBackground,
  {},
  '白色背景_隐藏底图',
  true
);


// 只显示ROI边框,避免遮挡影像
var roiOutline = ee.Image().byte().paint({
  featureCollection: ee.FeatureCollection([ee.Feature(roi)]),
  color: 1,
  width: 3
});

Map.addLayer(
  roiOutline,
  {palette: ['red']},
  'ROI_丰镇周边15km'
);


// ===============================
// 3. 设置两个时间窗口
// ===============================

// 前期:放宽窗口,允许重新找一景有效覆盖更完整的影像
var startDate1 = '2020-06-01';
var endDate1   = '2020-09-30';
var targetDate1 = '2020-08-15';

// 后期:保持原来的7-8月窗口
var startDate2 = '2024-07-01';
var endDate2   = '2024-08-31';
var targetDate2 = '2024-08-15';

// 文件名标识
var periodTag1 = '2020_0601_0930_single_valid';
var periodTag2 = '2024_0701_0831_single_valid';


// ===============================
// 4. 导出参数
// 丰镇大致位于UTM 49N,使用EPSG:32649
// ===============================

var exportFolder = 'GEE_Fengzhen_Landsat8_15km_single_valid';
var exportScale = 30;
var exportCrs = 'EPSG:32649';


// ===============================
// 5. 影像筛选参数
// ===============================

// 影像足迹至少覆盖ROI的比例
var minCoverageRatio = 0.99;

// 云掩膜后,ROI内有效像元比例
// 如果前期候选影像数量为0,可以改成0.85
var minValidRatio = 0.90;

// Landsat整景云量上限
var maxCloudCover = 80;


// ===============================
// 6. Landsat8 L2尺度因子
// ===============================

function applyScaleFactors(image) {
  image = ee.Image(image);

  var opticalBands = image.select([
      'SR_B1', 'SR_B2', 'SR_B3', 'SR_B4',
      'SR_B5', 'SR_B6', 'SR_B7'
    ])
    .multiply(0.0000275)
    .add(-0.2);

  var thermalBand = image.select('ST_B10')
    .multiply(0.00341802)
    .add(149.0);

  var out = image
    .addBands(opticalBands, null, true)
    .addBands(thermalBand, null, true);

  return ee.Image(out.copyProperties(image, image.propertyNames()));
}


// ===============================
// 7. 云、云影、雪、无效值、饱和像元掩膜
// ===============================

function maskL8L2(image) {
  image = ee.Image(image);

  var qa = image.select('QA_PIXEL');

  // QA_PIXEL位说明:
  // bit0: Fill
  // bit1: Dilated Cloud
  // bit2: Cirrus
  // bit3: Cloud
  // bit4: Cloud Shadow
  // bit5: Snow
  var qaMask = qa.bitwiseAnd(parseInt('111111', 2)).eq(0);

  // 去除辐射饱和像元
  var saturationMask = image.select('QA_RADSAT').eq(0);

  var out = image
    .updateMask(qaMask)
    .updateMask(saturationMask);

  return ee.Image(out.copyProperties(image, image.propertyNames()));
}


// ===============================
// 8. 计算NDVI
// ===============================

function addNDVI(image) {
  image = ee.Image(image);

  var ndvi = image.expression(
    '(NIR - RED) / (NIR + RED)',
    {
      'NIR': image.select('SR_B5'),
      'RED': image.select('SR_B4')
    }
  ).rename('NDVI');

  var out = image.addBands(ndvi);

  return ee.Image(out.copyProperties(image, image.propertyNames()));
}


// ===============================
// 9. 计算影像足迹覆盖ROI比例
// ===============================

function addCoverageRatio(image) {
  image = ee.Image(image);

  var roiArea = roi.area(1);
  var intersectArea = image.geometry().intersection(roi, 1).area(1);
  var coverageRatio = intersectArea.divide(roiArea);

  return ee.Image(image.set('ROI_COVERAGE_RATIO', coverageRatio));
}


// ===============================
// 10. 计算云掩膜后ROI内有效像元比例
// 这是关键:避免去云后只剩一小块
// ===============================

function addValidRatio(image) {
  image = ee.Image(image);

  var masked = ee.Image(maskL8L2(image));

  // 取红光波段mask作为有效像元掩膜
  // unmask(0)非常关键:无效区域计为0,否则均值可能虚高
  var validMask = masked.select('SR_B4')
    .mask()
    .rename('valid')
    .unmask(0);

  var validRatio = validMask.reduceRegion({
    reducer: ee.Reducer.mean(),
    geometry: roi,
    scale: exportScale,
    maxPixels: 1e13,
    tileScale: 4
  }).get('valid');

  return ee.Image(image.set('VALID_RATIO', validRatio));
}


// ===============================
// 11. 计算影像与目标日期的距离
// 方便优先选择接近目标日期的影像
// ===============================

function addDateDistanceAndScore(image, targetDate) {
  image = ee.Image(image);

  var dateDistance = ee.Date(image.get('system:time_start'))
    .difference(ee.Date(targetDate), 'day')
    .abs();

  var cloudCover = ee.Number(image.get('CLOUD_COVER'));
  var validRatio = ee.Number(image.get('VALID_RATIO'));

  // 分数越低越优:
  // 1. 有效像元比例越高越好
  // 2. 云量越低越好
  // 3. 日期越接近目标日期越好
  var score = ee.Number(1).subtract(validRatio).multiply(200)
    .add(cloudCover.multiply(0.2))
    .add(dateDistance.multiply(0.1));

  return ee.Image(image.set({
    'DATE_DISTANCE_DAYS': dateDistance,
    'SCENE_SCORE': score
  }));
}


// ===============================
// 12. 获取单景L2影像
// ===============================

function getSingleL2Scene(startDate, endDate, targetDate, label) {
  var collectionRaw = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
    .filterBounds(roi)
    .filterDate(startDate, endDate)
    .filter(ee.Filter.lt('CLOUD_COVER', maxCloudCover))
    .map(addCoverageRatio)
    .filter(ee.Filter.gte('ROI_COVERAGE_RATIO', minCoverageRatio))
    .map(addValidRatio)
    .filter(ee.Filter.gte('VALID_RATIO', minValidRatio))
    .map(function(img) {
      return addDateDistanceAndScore(img, targetDate);
    })
    .sort('SCENE_SCORE');

  print(label + '_有效候选影像数量', collectionRaw.size());
  print(label + '_有效候选影像列表', collectionRaw);

  var infoTable = collectionRaw.map(function(img) {
    img = ee.Image(img);
    return ee.Feature(null, {
      'DATE_ACQUIRED': img.get('DATE_ACQUIRED'),
      'CLOUD_COVER': img.get('CLOUD_COVER'),
      'ROI_COVERAGE_RATIO': img.get('ROI_COVERAGE_RATIO'),
      'VALID_RATIO': img.get('VALID_RATIO'),
      'DATE_DISTANCE_DAYS': img.get('DATE_DISTANCE_DAYS'),
      'SCENE_SCORE': img.get('SCENE_SCORE'),
      'LANDSAT_PRODUCT_ID': img.get('LANDSAT_PRODUCT_ID'),
      'WRS_PATH': img.get('WRS_PATH'),
      'WRS_ROW': img.get('WRS_ROW')
    });
  });

  print(label + '_候选影像日期云量有效像元表', infoTable);

  var raw = ee.Image(collectionRaw.first());

  print(label + '_最终选中影像', raw);
  print(label + '_DATE_ACQUIRED', raw.get('DATE_ACQUIRED'));
  print(label + '_CLOUD_COVER', raw.get('CLOUD_COVER'));
  print(label + '_ROI_COVERAGE_RATIO', raw.get('ROI_COVERAGE_RATIO'));
  print(label + '_VALID_RATIO', raw.get('VALID_RATIO'));
  print(label + '_DATE_DISTANCE_DAYS', raw.get('DATE_DISTANCE_DAYS'));
  print(label + '_SCENE_SCORE', raw.get('SCENE_SCORE'));
  print(label + '_LANDSAT_PRODUCT_ID', raw.get('LANDSAT_PRODUCT_ID'));

  var masked = ee.Image(maskL8L2(raw));
  var scaled = ee.Image(applyScaleFactors(masked));
  var withNDVI = ee.Image(addNDVI(scaled));
  var processed = ee.Image(withNDVI.clip(roi));

  return processed;
}


// ===============================
// 13. 获取两期单景影像
// ===============================

var image1 = ee.Image(getSingleL2Scene(
  startDate1,
  endDate1,
  targetDate1,
  '前期'
));

var image2 = ee.Image(getSingleL2Scene(
  startDate2,
  endDate2,
  targetDate2,
  '后期'
));

var ndvi1 = image1.select('NDVI').rename('NDVI_period1');
var ndvi2 = image2.select('NDVI').rename('NDVI_period2');


// ===============================
// 14. 统一NDVIsoil和NDVIveg,用于FVC计算
// 两期共用同一套参数,保证FVC可比
// ===============================

var ndviStats1 = ndvi1.reduceRegion({
  reducer: ee.Reducer.percentile([5, 95]),
  geometry: roi,
  scale: exportScale,
  maxPixels: 1e13,
  tileScale: 4
});

var ndviStats2 = ndvi2.reduceRegion({
  reducer: ee.Reducer.percentile([5, 95]),
  geometry: roi,
  scale: exportScale,
  maxPixels: 1e13,
  tileScale: 4
});

var ndviSoil1 = ee.Number(ndviStats1.get('NDVI_period1_p5'));
var ndviVeg1  = ee.Number(ndviStats1.get('NDVI_period1_p95'));

var ndviSoil2 = ee.Number(ndviStats2.get('NDVI_period2_p5'));
var ndviVeg2  = ee.Number(ndviStats2.get('NDVI_period2_p95'));

var ndviSoil = ndviSoil1.min(ndviSoil2);
var ndviVeg  = ndviVeg1.max(ndviVeg2);

print('前期NDVI_5%', ndviSoil1);
print('前期NDVI_95%', ndviVeg1);
print('后期NDVI_5%', ndviSoil2);
print('后期NDVI_95%', ndviVeg2);
print('统一NDVIsoil', ndviSoil);
print('统一NDVIveg', ndviVeg);


// ===============================
// 15. 计算两期FVC
// FVC = (NDVI - NDVIsoil) / (NDVIveg - NDVIsoil)
// ===============================

var fvc1 = ndvi1
  .subtract(ndviSoil)
  .divide(ndviVeg.subtract(ndviSoil))
  .clamp(0, 1)
  .rename('FVC_period1');

var fvc2 = ndvi2
  .subtract(ndviSoil)
  .divide(ndviVeg.subtract(ndviSoil))
  .clamp(0, 1)
  .rename('FVC_period2');


// ===============================
// 16. 计算NDVI和FVC差异
// 差异 = 后期 - 前期
// ===============================

var ndviDiff = ndvi2
  .subtract(ndvi1)
  .rename('NDVI_diff_period2_minus_period1');

var fvcDiff = fvc2
  .subtract(fvc1)
  .rename('FVC_diff_period2_minus_period1');


// ===============================
// 17. 5级可视化分级函数
// 注意:这些分级影像只用于地图显示,不参与连续值导出
// ===============================

function classifyNDVI5(ndvi) {
  ndvi = ee.Image(ndvi);

  var cls = ee.Image(1)
    .where(ndvi.gt(0.10).and(ndvi.lte(0.30)), 2)
    .where(ndvi.gt(0.30).and(ndvi.lte(0.50)), 3)
    .where(ndvi.gt(0.50).and(ndvi.lte(0.70)), 4)
    .where(ndvi.gt(0.70), 5)
    .updateMask(ndvi.mask())
    .rename('NDVI_class_5');

  return ee.Image(cls);
}


function classifyFVC5(fvc) {
  fvc = ee.Image(fvc);

  var cls = ee.Image(1)
    .where(fvc.gt(0.20).and(fvc.lte(0.40)), 2)
    .where(fvc.gt(0.40).and(fvc.lte(0.60)), 3)
    .where(fvc.gt(0.60).and(fvc.lte(0.80)), 4)
    .where(fvc.gt(0.80), 5)
    .updateMask(fvc.mask())
    .rename('FVC_class_5');

  return ee.Image(cls);
}


function classifyDiff5(diff) {
  diff = ee.Image(diff);

  var cls = ee.Image(3)
    .where(diff.lte(-0.20), 1)
    .where(diff.gt(-0.20).and(diff.lte(-0.05)), 2)
    .where(diff.gt(-0.05).and(diff.lte(0.05)), 3)
    .where(diff.gt(0.05).and(diff.lte(0.20)), 4)
    .where(diff.gt(0.20), 5)
    .updateMask(diff.mask())
    .rename('diff_class_5');

  return ee.Image(cls);
}


var ndviClass1 = classifyNDVI5(ndvi1);
var ndviClass2 = classifyNDVI5(ndvi2);

var fvcClass1 = classifyFVC5(fvc1);
var fvcClass2 = classifyFVC5(fvc2);

var ndviDiffClass = classifyDiff5(ndviDiff);
var fvcDiffClass = classifyDiff5(fvcDiff);


// ===============================
// 18. 提取L2多波段数据
// 只导出L2地表反射率和地表温度
// 不包含NDVI和FVC
// ===============================

var l2Bands1 = image1.select([
  'SR_B1', 'SR_B2', 'SR_B3', 'SR_B4',
  'SR_B5', 'SR_B6', 'SR_B7',
  'ST_B10'
]).toFloat();

var l2Bands2 = image2.select([
  'SR_B1', 'SR_B2', 'SR_B3', 'SR_B4',
  'SR_B5', 'SR_B6', 'SR_B7',
  'ST_B10'
]).toFloat();


// ===============================
// 19. 可视化检查
// Map.addLayer只影响显示,不影响导出TIF真实值
// ===============================

Map.addLayer(
  image1.select(['SR_B4', 'SR_B3', 'SR_B2']),
  {min: 0.02, max: 0.30},
  '前期L2真彩色_单景'
);

Map.addLayer(
  image2.select(['SR_B4', 'SR_B3', 'SR_B2']),
  {min: 0.02, max: 0.30},
  '后期L2真彩色_单景'
);


// NDVI五级可视化
Map.addLayer(
  ndviClass1,
  {
    min: 1,
    max: 5,
    palette: [
      '#d73027',
      '#fc8d59',
      '#fee08b',
      '#91cf60',
      '#1a9850'
    ]
  },
  '前期NDVI_五级显示'
);

Map.addLayer(
  ndviClass2,
  {
    min: 1,
    max: 5,
    palette: [
      '#d73027',
      '#fc8d59',
      '#fee08b',
      '#91cf60',
      '#1a9850'
    ]
  },
  '后期NDVI_五级显示'
);


// FVC五级可视化
Map.addLayer(
  fvcClass1,
  {
    min: 1,
    max: 5,
    palette: [
      '#f7f7f7',
      '#ffffb2',
      '#fecc5c',
      '#78c679',
      '#006837'
    ]
  },
  '前期FVC_五级显示'
);

Map.addLayer(
  fvcClass2,
  {
    min: 1,
    max: 5,
    palette: [
      '#f7f7f7',
      '#ffffb2',
      '#fecc5c',
      '#78c679',
      '#006837'
    ]
  },
  '后期FVC_五级显示'
);


// NDVI差异五级可视化
Map.addLayer(
  ndviDiffClass,
  {
    min: 1,
    max: 5,
    palette: [
      '#b2182b',
      '#ef8a62',
      '#f7f7f7',
      '#67a9cf',
      '#2166ac'
    ]
  },
  'NDVI差异_五级显示'
);


// FVC差异五级可视化
Map.addLayer(
  fvcDiffClass,
  {
    min: 1,
    max: 5,
    palette: [
      '#b2182b',
      '#ef8a62',
      '#f7f7f7',
      '#67a9cf',
      '#2166ac'
    ]
  },
  'FVC差异_五级显示'
);


// ===============================
// 20. 均值统计,方便检查和写报告
// ===============================

var meanStats = ee.Image.cat([
  ndvi1, ndvi2, ndviDiff,
  fvc1, fvc2, fvcDiff
]).reduceRegion({
  reducer: ee.Reducer.mean(),
  geometry: roi,
  scale: exportScale,
  maxPixels: 1e13,
  tileScale: 4
});

print('NDVI和FVC均值统计', meanStats);


// ===============================
// 21. 导出:L2多波段
// ===============================

Export.image.toDrive({
  image: l2Bands1,
  description: 'Fengzhen_15km_L8_L2_Multiband_' + periodTag1,
  folder: exportFolder,
  fileNamePrefix: 'Fengzhen_15km_L8_L2_Multiband_' + periodTag1,
  region: roi,
  scale: exportScale,
  crs: exportCrs,
  maxPixels: 1e13
});

Export.image.toDrive({
  image: l2Bands2,
  description: 'Fengzhen_15km_L8_L2_Multiband_' + periodTag2,
  folder: exportFolder,
  fileNamePrefix: 'Fengzhen_15km_L8_L2_Multiband_' + periodTag2,
  region: roi,
  scale: exportScale,
  crs: exportCrs,
  maxPixels: 1e13
});


// ===============================
// 22. 导出:NDVI连续值
// ===============================

Export.image.toDrive({
  image: ndvi1.toFloat(),
  description: 'Fengzhen_15km_NDVI_' + periodTag1,
  folder: exportFolder,
  fileNamePrefix: 'Fengzhen_15km_NDVI_' + periodTag1,
  region: roi,
  scale: exportScale,
  crs: exportCrs,
  maxPixels: 1e13
});

Export.image.toDrive({
  image: ndvi2.toFloat(),
  description: 'Fengzhen_15km_NDVI_' + periodTag2,
  folder: exportFolder,
  fileNamePrefix: 'Fengzhen_15km_NDVI_' + periodTag2,
  region: roi,
  scale: exportScale,
  crs: exportCrs,
  maxPixels: 1e13
});


// ===============================
// 23. 导出:FVC连续值
// ===============================

Export.image.toDrive({
  image: fvc1.toFloat(),
  description: 'Fengzhen_15km_FVC_' + periodTag1,
  folder: exportFolder,
  fileNamePrefix: 'Fengzhen_15km_FVC_' + periodTag1,
  region: roi,
  scale: exportScale,
  crs: exportCrs,
  maxPixels: 1e13
});

Export.image.toDrive({
  image: fvc2.toFloat(),
  description: 'Fengzhen_15km_FVC_' + periodTag2,
  folder: exportFolder,
  fileNamePrefix: 'Fengzhen_15km_FVC_' + periodTag2,
  region: roi,
  scale: exportScale,
  crs: exportCrs,
  maxPixels: 1e13
});


// ===============================
// 24. 导出:NDVI差异和FVC差异连续值
// ===============================

Export.image.toDrive({
  image: ndviDiff.toFloat(),
  description: 'Fengzhen_15km_NDVI_Diff_' + periodTag2 + '_minus_' + periodTag1,
  folder: exportFolder,
  fileNamePrefix: 'Fengzhen_15km_NDVI_Diff_' + periodTag2 + '_minus_' + periodTag1,
  region: roi,
  scale: exportScale,
  crs: exportCrs,
  maxPixels: 1e13
});

Export.image.toDrive({
  image: fvcDiff.toFloat(),
  description: 'Fengzhen_15km_FVC_Diff_' + periodTag2 + '_minus_' + periodTag1,
  folder: exportFolder,
  fileNamePrefix: 'Fengzhen_15km_FVC_Diff_' + periodTag2 + '_minus_' + periodTag1,
  region: roi,
  scale: exportScale,
  crs: exportCrs,
  maxPixels: 1e13
});

生成结果如下

点击右下角layer可以选择可视化展示对应的图像

右上角task可以看到生成的结果,点击Run将其保存在google cloud对应的项目之下,即可进入对应的项目进行下载使用

NDVI可视化结果

FCV植被覆盖度可视化结果

NDVI变化可视化结果

想制作其他区域的将对应的经纬度范围更换即可

相关推荐
hkj88081 小时前
CRC-512算法输出64字节
算法
张二娃同学1 小时前
第08篇_RNN_LSTM_GRU序列模型
人工智能·python·rnn·深度学习·神经网络·gru·lstm
财经资讯数据_灵砚智能1 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(夜间-次晨)2026年5月13日
大数据·人工智能·python·信息可视化·语言模型·自然语言处理
我鑫如一1 小时前
专业的AI API中转站厂家
人工智能·python
@我漫长的孤独流浪1 小时前
计算机系统核心概念与性能优化全解析
算法·计算机外设
如竟没有火炬1 小时前
接雨水22
数据结构·python·算法·leetcode·散列表
消晨消晨1 小时前
Pytorch初上手——Dataset自定义数据集与Dataloader数据加载器
人工智能·pytorch·python
ʚ希希ɞ ྀ2 小时前
二叉树的锯齿层序遍历
数据结构·算法
小白学大数据2 小时前
均线选股策略研究:基于 Python 数据分析实现
人工智能·python·数据分析
C137的本贾尼2 小时前
从零认识 Spring AI:Java 开发者的 AI 第一课
python·langchain