OpenLayers 计算GeoTIFF影像NDVI

前言

NDVI(Normalized Difference Vegetation Index)即归一化植被指数,是反应农作物长势和营养信息的重要参数之一,用于监测植物生长状态、植被覆盖度和消除部分辐射误差。其值在-1,1之间,-1表示可见光高反射;0表示有岩石或裸土等,NIR和R近似相等;正值,表示有植被覆盖,且值越大,表明植被覆盖度越高。计算公式:NDVI = (NIR - RED)/ (NIR + RED)

1. 加载数据源

Landsat卫星影像中,B4为红色通道,B5为近红外通道,所以加载B4B5通道数据,并设置无值数据为0,设置图层为不透明度。

php 复制代码
const source = new ol.source.GeoTIFF({
    sources: [
        {
            // red band
            url: "http://localhost/GeoTIFF/LC08_L2SP_129043_20211120_20211130_02_T1/LC08_L2SP_129043_20211120_20211130_02_T1_SR_B4.TIF",
            max: 40000
        },
        {
            // near infraed
            url: "http://localhost/GeoTIFF/LC08_L2SP_129043_20211120_20211130_02_T1/LC08_L2SP_129043_20211120_20211130_02_T1_SR_B5.TIF",
            max: 45000
        }
    ],
    nodata: 0, // 设置无效数据为0
    opaque: true, // 设置不透明
    // convertToRGB: true // 将色彩系统转换为RGB
})

2. 加载GeoTIFF图层

根据公式计算NDVI值,并进行可视化表达。其中['/', value1, value2]表示用value2除以value1['-', ['band', 2], ['band', 1]]表示用band2-band1,也就是近红外减去红外,['+', ['band', 2], ['band', 1]]表示band2+band1,也就是近红外加上红外。

csharp 复制代码
const tiffLayer = new ol.layer.WebGLTile({
    source: source,
    style: {
        color: [
            'interpolate',
            ['linear'],
            // 计算NDVI值
            ['/', ['-', ['band', 2], ['band', 1]], ['+', ['band', 2], ['band', 1]]],
            // NDVI色带,其值为[-1,1]
            -0.2,
            [191, 191, 191],
            -0.1,
            [219, 219, 219],
            0,
            [255, 255, 224],
            0.025,
            [255, 250, 204],
            0.05,
            [237, 232, 181],
            0.075,
            [222, 217, 156],
            0.1,
            [204, 199, 130],
            0.125,
            [189, 184, 107],
            0.15,
            [176, 194, 97],
            0.175,
            [163, 204, 89],
            0.2,
            [145, 191, 82],
            0.25,
            [128, 179, 71],
            0.3,
            [112, 163, 64],
            0.35,
            [97, 150, 54],
            0.4,
            [79, 138, 46],
            0.45,
            [64, 125, 36],
            0.5,
            [48, 110, 28],
            0.55,
            [33, 97, 18],
            0.6,
            [15, 84, 10],
            0.65,
            [0, 69, 0],
        ]
    }
})

3. 显示NDVI值

鼠标在地图上移动或者点击地图时,显示NDVI值。

kotlin 复制代码
const displayPixelValue = (event) => {
  // 返回像素位置数据,根据数据类型而定
  const data = tiffLayer.getData(event.pixel)
  if (!data) {
    return
  }
  const red = data[0]
  const nir = data[1]
  const ndvi = (nir - red) / (nir + red)
  const ndviInput = document.querySelector(".layui-input")
  ndviInput.value = ndvi.toFixed(4)
}
// 监听鼠标移动和点击事件
map.on(["pointermove", "click"], displayPixelValue)

4. 完整代码

其中libs文件夹下的包需要更换为自己下载的本地包或者引用在线资源。本示例引用了layui组件,请自行替换。

xml 复制代码
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>OpenLayers 根据Landsat计算NDVI</title>
    <meta charset="utf-8" />

    <script src="../../../libs/proj4.js"></script>
    <script src="../../../js/ol9.2.4.js"></script>
    <script src="../../../libs/layui/layui.js"></script>

    <link rel="stylesheet" href="../../../css/ol9.2.4.css">
    <link rel="stylesheet" href="../../../libs/layui/css/layui.css">

    <script src="https://cdn.jsdelivr.net/npm/geotiff@2.1.3/dist-browser/geotiff.min.js"></script>
    <style>
        * {
            padding: 0;
            margin: 0;
            font-size: 14px;
            font-family: '微软雅黑';
        }

        .clearfix::after {
            display: block;
            content: "";
            clear: both;
        }

        html,
        body {
            width: 100%;
            height: 100%;
        }

        #map {
            position: absolute;
            top: 50px;
            bottom: 0;
            width: 100%;
        }

        #top-content {
            position: absolute;
            width: 100%;
            height: 50px;
            line-height: 50px;
            background: linear-gradient(135deg, #ff00cc, #ffcc00, #00ffcc, #ff0066);
            color: #fff;
            text-align: center;
            font-size: 32px;
        }

        #top-content span {
            font-size: 32px;
        }

        #layer-container {
            position: absolute;
            top: 20%;
            left: 20px;
            width: 250px;
            padding: 10px;
            background: #fff;
            color: #fff;
            border: 1px solid #ddd;
            border-radius: 2.5px;
        }

        .layer-head {
            background: #16baaa;
            padding: 10px;
            margin-bottom: 15px;
        }

        .layer-form {
            width: 100%;
        }

        .layui-form-label {
            width: 44%;
            padding: 8px 15px;
            height: 38px;
            line-height: 20px;
            border-width: 1px;
            border-style: solid;
            border-radius: 2px 0 0 2px;
            text-align: center;
            background-color: #fafafa;
            overflow: hidden;
            white-space: nowrap;
            text-overflow: ellipsis;
            box-sizing: border-box;
            border-color: #eee;
            font-weight: 400;
            color: #000;
        }

        .layui-form-item {
            margin-bottom: 0;
        }

        .layui-input[disabled] {
            background-color: #fff;
        }
    </style>
</head>

<body>
    <div id="top-content">
        <span>OpenLayers 根据Landsat计算NDVI</span>
    </div>
    <div id="map" title="地图显示"></div>
    <div id="layer-container">
        <div class="layer-form">
            <div class="layui-form-item">
                <label class="layui-form-label">NDVI值</label>
                <div class="layui-input-block">
                    <input type="number" disabled lay-affix="number" min="0" max="1" placeholder="显示NDVI值"
                        class="layui-input rectangle-layer">
                </div>
            </div>
        </div>
    </div>
</body>

</html>

<script>
    const source = new ol.source.GeoTIFF({
        sources: [
            {
                // red band
                url: "http://localhost/GeoTIFF/LC08_L2SP_129043_20211120_20211130_02_T1/LC08_L2SP_129043_20211120_20211130_02_T1_SR_B4.TIF",
                max: 40000
            },
            {
                // near infraed
                url: "http://localhost/GeoTIFF/LC08_L2SP_129043_20211120_20211130_02_T1/LC08_L2SP_129043_20211120_20211130_02_T1_SR_B5.TIF",
                max: 45000
            }
        ],
        nodata: 0, // 设置无效数据为0
        opaque: false, // 开启透明
        // convertToRGB: true // 将色彩系统转换为RGB
    })
    // 加载GeoTIFF影像数据
    const tiffLayer = new ol.layer.WebGLTile({
        source: source,
        style: {
            color: [
                'interpolate',
                ['linear'],
                // 计算NDVI值
                ['/', ['-', ['band', 2], ['band', 1]], ['+', ['band', 2], ['band', 1]]],
                // NDVI色带,其值为[-1,1]
                -0.2,
                [191, 191, 191],
                -0.1,
                [219, 219, 219],
                0,
                [255, 255, 224],
                0.025,
                [255, 250, 204],
                0.05,
                [237, 232, 181],
                0.075,
                [222, 217, 156],
                0.1,
                [204, 199, 130],
                0.125,
                [189, 184, 107],
                0.15,
                [176, 194, 97],
                0.175,
                [163, 204, 89],
                0.2,
                [145, 191, 82],
                0.25,
                [128, 179, 71],
                0.3,
                [112, 163, 64],
                0.35,
                [97, 150, 54],
                0.4,
                [79, 138, 46],
                0.45,
                [64, 125, 36],
                0.5,
                [48, 110, 28],
                0.55,
                [33, 97, 18],
                0.6,
                [15, 84, 10],
                0.65,
                [0, 69, 0],
            ]
        }
    })

    source.getView().then(res => {
        console.log(res)
    })

    const map = new ol.Map({
        target: "map",
        loadTilesWhileInteracting: true,
        view: source.getView(),
        // 地图默认控件
        controls: ol.control.defaults.defaults({
            zoom: false,
            attribution: true,
            rotate: true
        })
    })

    map.addLayer(tiffLayer)

    const displayPixelValue = (event) => {
        // 返回像素位置数据,根据数据类型而定
        const data = tiffLayer.getData(event.pixel)
        if (!data) {
            return
        }
        const red = data[0]
        const nir = data[1]
        const ndvi = (nir - red) / (nir + red)
        const ndviInput = document.querySelector(".layui-input")
        ndviInput.value = ndvi.toFixed(4)
    }

    map.on(["pointermove", "click"], displayPixelValue)
</script>

如何下载Landsat卫星影像请参考:https://mp.weixin.qq.com/s?__biz=MzkwOTEzNjI5NQ==&mid=2247485131&idx=1&sn=6bf2af3b75f98a573ccc96a347fe837a&chksm=c13e1e03f649971529861ab97834750ea1bc824f992a32f3325f74a6a3174309b64cffb8f759#rd

如何加载GeoTIFF影像请参考:https://mp.weixin.qq.com/s?__biz=MzkwOTEzNjI5NQ==&mid=2247485173&idx=1&sn=2e52d6df4ee71b67e1db8e32c9a22693&chksm=c13e1e3df649972b6f9edb7d7b2a452d9edcd81c535f88d1ffce7f480884f7a2fffdc017bfd9#rd

如何加载GeoTIFF投影坐标请参考:https://mp.weixin.qq.com/s?__biz=MzkwOTEzNjI5NQ==&mid=2247485224&idx=1&sn=6d4e81a1f6e98deece1f427c9737542f&chksm=c13e1fe0f64996f6ed98101539b5270e0076bc933f05769f956f33059025dff252dbe7666501#rd

相关推荐
狂炫冰美式10 分钟前
人均配了AI, 为什么公司还是没变快? 🤔 本质还是分布式系统问题
前端·后端·架构
乘风gg1 小时前
多 Agent 不是万能的!搞懂这 5 个原则,少走 1 年弯路!
前端·agent·ai编程
猩猩程序员2 小时前
Vercel 推出 Agent 框架 Eve:让 AI Agent 像写 Web 应用一样简单
前端
爱读源码的大都督2 小时前
Claude Code源码分析(三):为什么系统提示词中需要有tools呢?
前端·人工智能·后端
爱勇宝2 小时前
Claude Code 被曝暗藏“隐形检测”代码:封代理不是最可怕的,可怕的是你根本不知道它在干什么
前端·后端·程序员
小牛不牛的程序员3 小时前
我用 Claude Code 半天撸完了一个完整网站,AI 编程到底提升了多少效率?
前端
东风破_3 小时前
JavaScript 面试常考的字符串算法:从反转字符串到回文判断
前端·javascript
ITOM运维行者3 小时前
从零搭建企业级服务器监控体系:踩坑实录与架构设计
前端·后端
monologues3 小时前
深入 Vue 3 源码:响应式系统的精妙设计与编译优化
前端
hunterandroid3 小时前
Paging 3 分页:从手动分页到声明式加载
前端