关于使用天地图、leaflet、ENVI、Vue工具实现 前端地图上覆盖上处理的农业地块图层任务
1.项目框架搭建
项目地址:Webgis: 一个关于webgis、天地图、Leaflet、Vue、数据库的学习框架。
①git到本地,vscode打开。
② 配置后端
搜索下载MySQL插件(前提:电脑中装有MySQL才可应用)。
连接数据库。
配置基本信息(如:ip、port、账号、密码)。
保存,连接(如下图:连接成功)。
下图:
1.api:定义接口,启动数据库服务。
2.config:数据库配置文件。
3.models:数据库遥感数据表。
首先配置连接信息(如)。
创建conda环境,再安装必要依赖。
点击运行,创建表,提示连接成功。
③配置遥感图层处理脚本
配置环境(其中 osgeo比较麻烦,单独给出),修改文件路径,运行脚本。
conda install -c conda-forge gdal
参考:osgeo python安装入门实例_osgeo库安装-CSDN博客
配置读取路径(个人问题),点击运行脚本。
遥感处理文件夹下生成out_geotiff文件夹、前端的public下也生成out_geotiff文件夹。
脚本具体实现,看后续...
④配置前端
配置环境,安装必要依赖(不是conda环境)。
运行(npm run serve),看到前端页面。
2.项目框架代码学习
①前端
导入基本地图
<template>
<div id="baseMap" style="height: 100vh; width: 100%; "></div>
</template>
<script>
//地图
import { onMounted, ref } from "vue";
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import 'leaflet.chinatmsproviders';
import 'leaflet-geotiff';
import '@/utils/leaflet-geotiff-plotty.js';
export default {
setup() {
// 地图初始化
const MapKey = "4be8e59596e4cee92183d2a61d77ce0c";
// const MapKey = "d70045af77b5a8c2fac6a69b80ffdd7a";
const map = ref(null);
const layerVisibility = ref([false, false, false]);
const allLayers = ref([]); // 存储所有加载的遥感图层
const layerGroup = ref(L.layerGroup()).value; // 使用 layerGroup 管理图层
onMounted(() => {
// 初始化地图
map.value = L.map("baseMap", {
center: L.latLng(45.757000, 126.642000),//哈尔滨
// center: L.latLng(45.75075431739313, 127.5517068330741),//宾县
//center: L.latLng(46.05075431739313,126.8517068330741),//呼兰区
//center: L.latLng(55.23545507478532, 10.106575515900523),//田块分割
//center: L.latLng(29.676840, -95.369222),
zoom: 8,
zoomControl: true,
attributionControl: false,
});
// 加载天地图基础图层
let satelliteTileLayer = L.layerGroup([
L.tileLayer.chinaProvider('TianDiTu.Satellite.Map', { key: MapKey }),
L.tileLayer.chinaProvider('TianDiTu.Satellite.Annotion', { key: MapKey })
]);
// 加载遥感图层
satelliteTileLayer.addTo(map.value);
layerGroup.addTo(map.value); // 将 layerGroup 添加到地图
});
return {
map,
layerGroup,
layerVisibility, // 添加到返回对象中
allLayers,
};
}
};
</script>
测试页面:
测试代码:
<template>
<div class="container">
<div>
<h1>WebGIS Test</h1>
</div>
<div style="margin-bottom: 20px;">
<!-- 农田遥感指数图层选择框 -->
<el-col :span="3.5" v-for="type in ['NDVI', 'WET', 'NDBSI']" :key="type">
<el-button style="width: 130px;" @click="toggleLayer(type, 'qixingnongchang')">{{ type }}</el-button>
</el-col>
<!-- 隐藏所有图层 -->
<el-col :span="2.5">
<el-button style="width: 120px;" @click="hideAllLayers">隐藏所有图层</el-button>
</el-col>
</div>
<div id="baseMap" style="height: 500px; width: 50%; "></div>
</div>
</template>
<script>
import { onMounted, ref } from "vue";
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import 'leaflet.chinatmsproviders';
import 'leaflet-geotiff';
import '@/assets/leaflet-geotiff-plotty.js';
import axios from "axios";
export default {
data() {
return {
activeName: 'third',
currentTime: '',
data: [], // 用于存储从 API 获取的返回遥感数据
activeLayers: [],
};
},
methods: {
// 根据 activeName 获取对应日期
getDateByActiveName() {
const dateMap = {
first: "2024-05-03",
second: "2024-06-04",
third: "2024-07-14",
fourth: "2024-08-31",
fifth: "2024-09-24",
sixth: "2024-10-26",
};
console.log("1.获取对应日期", dateMap[this.activeName]);
return dateMap[this.activeName] || null;
},
//展示图层
async toggleLayer(type, FieldName) {
//layerKey:图层名称
console.log("type:", type, "FieldName:", FieldName);
const layerKey = `${this.getDateByActiveName()}_${type}_${FieldName}`; // 使用 date、type 和 FieldName 组合作为唯一标识
console.log("2.layerKey: ", layerKey)
// 如果图层已经显示,则隐藏,否则加载并显示
if (this.layerVisibility[layerKey]) {
console.log('3.隐藏图层:', layerKey);
this.hideLayer(layerKey); // 隐藏图层
} else {
console.log('3.显示图层:', layerKey);
// 将当前激活的图层标识符 layerKey 添加到 activeLayers 数组中。
this.activeLayers.push(layerKey); // 将图层标记为活跃
/**
* await 是用于等待一个 Promise 对象完成的关键字。它只能在 async 函数内部使用。当执行到这一行代码时,程序会暂停执行,直到 fetchData 函数执行完成并返回结果。
* fetchData 是一个异步函数,其主要功能是向服务器发送请求以获取遥感数据,并将这些数据加载为 GeoTIFF 图层以在地图上进行显示。
*/
await this.fetchData(type, FieldName); // 加载并显示图层
}
},
/**
*
* @param type 遥感指数类型
* @param FieldName
* 首先获取当前的日期,然后使用 Axios 库向指定的 API 发送一个 POST 请求,包含必要的过滤条件(例如日期、类型和字段名)。
* 通过 API 返回的数据将被处理,并转换为地图可显示的图层。
* 图层加载完成后,将图层添加到 layerGroup 中,并设置图层可见性。
*/
async fetchData(type, FieldName) {
const date1 = this.getDateByActiveName();
console.log("4.对应日期为:", date1);
const filters = { Date: date1, type: type, FieldName: FieldName, };
try {
// 通过向指定的 API 发送 POST 请求来获取遥感数据。
const response = await axios.post('http://127.0.0.1:5002/api/remote_sensing', filters);
// 将 API 返回的数据存储在 data 数组中
this.data = response.data;
console.log('5.Data fetched:', response.data);
// 遍历数据并加载为 GeoTIFF 图层
/**
* 1遍历数据数组:this.data.forEach((layerData) => { 使用forEach方法遍历this.data数组。this.data包含从API获取的遥感数据,而layerData是当前遍历到的每一个数据项。
*
* 2遍历每一项数据,获取其FileNameUrl、SouthWestLatitude、SouthWestLongitude、NorthEastLatitude、NorthEastLongitude、Max、Min等属性。
* FileNameUrl: GeoTIFF文件的URL地址,用于加载图层。
* Max和Min: 用于显示图层数据的最小值和最大值。
*
* 3定义边界坐标:创建一个bounds数组来定义图层的地理范围。其中,西南角和东北角的经纬度分别被转换为浮点数(使用parseFloat),以确保它们是数值型数据。这是重要的一步,因为地图需要准确的地理坐标来渲染。
*/
this.data.forEach((layerData) => {
const { FileNameUrl, SouthWestLatitude, SouthWestLongitude, NorthEastLatitude, NorthEastLongitude, Max, Min } = layerData;
const bounds = [
[parseFloat(SouthWestLatitude), parseFloat(SouthWestLongitude)],
[parseFloat(NorthEastLatitude), parseFloat(NorthEastLongitude)],
];
/**
* bounds 属性指定了图层的地理范围,即这个图层在地图上显示的区域。
* renderer 属性指定了图层的渲染方式。这里使用了 plotty 渲染器,它可以将遥感数据转换为可视化的图像。
* L.LeafletGeotiff.plotty 是一个 Leaflet 插件,用于将 GeoTIFF 数据转换为可视化的图层。
* options 对象包含了图层的边界、渲染器和透明度。
* opacity 属性指定了图层的透明度。
*/
const options = {
bounds: bounds,
renderer: L.LeafletGeotiff.plotty({
displayMin: parseFloat(Min),
displayMax: parseFloat(Max),
colorScale: "viridis",
noDataValue: -1,
}),
opacity: 1,
};
/**
* 1创建 GeoTIFF 图层:使用 L.leafletGeotiff 方法创建了一个 GeoTIFF 图层。FileNameUrl 是图层数据的 URL(指向一个 GeoTIFF 文件),而 options 是一个配置对象,包含了边界、渲染器和透明度等信息。
* 2将图层添加到 layerGroup 中:使用 addLayer 方法将图层添加到 layerGroup 中。
* 3allLayers 数组: 存储图层和类型:将图层和类型存储在 allLayers 数组中。
* 4设置图层可见性:将图层的可见性设置为 true。
*/
// 创建 GeoTIFF 图层
const geoTiffLayer = L.leafletGeotiff(FileNameUrl, options);
console.log('6.FileNameUrl:', FileNameUrl, 'bounds:', bounds, 'options:', options);
console.log('6.geoTiffLayer:', geoTiffLayer);
this.layerGroup.addLayer(geoTiffLayer); // 添加图层到 layerGroup
console.log('6.layerGroup:', this.layerGroup);
const layerKey = `${this.getDateByActiveName()}_${type}_${FieldName}`; // 使用 date0、type 和 number 组合作为唯一标识
this.allLayers.push({ layer: geoTiffLayer, type: layerKey }); // 存储图层和类型
console.log('6.allLayers:', this.allLayers);
this.layerVisibility[type] = true;
/**
* Q:为什么FileNameUrl是 output_geotiff\20240714_RemoteSensingIndex\20240714_qixingnongchang_NDVI_geotiff.tif,但是可以访问到public文件夹?
* A:因为FileNameUrl是API返回的路径,而public文件夹是后端服务的静态资源目录,两者是两个不同的概念。API返回的路径是相对于后端服务的,而public文件夹是相对于前端页面的。因此,当API返回的路径指向public文件夹中的文件时,前端页面可以通过该路径访问到该文件。
* 后端返回数据:
* result
* [{'id': 23, 'Createtime': '2024-11-27 11:10:02',
*'FileName': '20240714_qixingnongchang_NDVI_geotiff.tif',
*'FileNameUrl': 'output_geotiff\\20240714_RemoteSensingIndex\\20240714_qixingnongchang_NDVI_geotiff.tif',
*'type': 'NDVI', 'SouthWestLatitude': 47.0486, 'SouthWestLongitude': 132.631, 'NorthEastLatitude': 47.2315, 'NorthEastLongitude': 133.183, 'Max': 0.908199, 'Min': 0.000760456, 'Mean': 0.738675, 'Date': '2024-07-14', 'FieldName': 'qixingnongchang'}]
*
* 前端页面访问路径:http://127.0.0.1:5000/public/output_geotiff/20240714_RemoteSensingIndex/20240714_qixingnongchang_NDVI_geotiff.tif
*/
/**
* 设置中心点和缩放级别:使用 setView 方法设置中心点和缩放级别。这里使用了经纬度和缩放级别的平均值来设置中心点和缩放级别。
* 这里的经纬度和缩放级别的计算方法是:
* 1. 计算中心点的经纬度:使用 SouthWestLatitude、SouthWestLongitude、NorthEastLatitude、NorthEastLongitude 四个属性的平均值来计算中心点的经纬度。
* 2. 计算缩放级别:根据中心点的经纬度和边界坐标的距离来计算缩放级别。如果边界坐标的距离小于 0.1 公里,则使用 11 级,否则使用 13 级。
*/
const x = (parseFloat(SouthWestLatitude) + parseFloat(NorthEastLatitude)) / 2;
const y = (parseFloat(SouthWestLongitude) + parseFloat(NorthEastLongitude)) / 2;
const z = (parseFloat(NorthEastLatitude) - parseFloat(SouthWestLatitude));
this.map.setView(L.latLng(x, y), z > 0.1 ? 11 : 13);
console.log(`时期:${date1},显示图层: ${type},名称:${FieldName}`);
this.layerVisibility[layerKey] = true; // 更新图层可见状态
});
} catch (error) {
console.error('Error fetching data:', error);
}
},
// 隐藏图层
hideLayer(layerKey) {
//const layerKey = `${this.getDateByActiveName()}_${type}_${FieldName}`; // 使用 date0、type 和 number 组合作为唯一标识
// 遍历 allLayers 数组,找到匹配的图层并隐藏
this.allLayers = this.allLayers.filter(({ layer, type: layerType }) => {
console.log(layerKey)
// 如果图层类型和 layerKey 匹配,则隐藏图层并返回 false,否则返回 true
if (layerType === layerKey) {
this.layerGroup.removeLayer(layer);
this.layerVisibility[layerKey] = false;
return false;
}
return true;
});
},
// 隐藏所有图层
hideAllLayers() {
// 遍历 allLayers 数组,隐藏所有图层
this.allLayers.forEach(({ layer }) => {
// 隐藏每个图层:
this.layerGroup.removeLayer(layer);
});
this.allLayers = [];
Object.keys(this.layerVisibility).forEach((key) => {
this.layerVisibility[key] = false;
});
console.log("已隐藏所有图层");
},
},
setup() {
// 地图初始化
// const MapKey = "4be8e59596e4cee92183d2a61d77ce0c";
const MapKey = "d70045af77b5a8c2fac6a69b80ffdd7a";
const map = ref(null);
const layerVisibility = ref([false, false, false]);
const allLayers = ref([]); // 存储所有加载的遥感图层
const layerGroup = ref(L.layerGroup()).value; // 使用 layerGroup 管理图层
onMounted(() => {
// 初始化地图
map.value = L.map("baseMap", {
center: L.latLng(45.757000, 126.642000),//哈尔滨
// center: L.latLng(45.75075431739313, 127.5517068330741),//宾县
//center: L.latLng(46.05075431739313,126.8517068330741),//呼兰区
//center: L.latLng(55.23545507478532, 10.106575515900523),//田块分割
//center: L.latLng(29.676840, -95.369222),
zoom: 8,
zoomControl: true,
attributionControl: false,
});
// 加载天地图基础图层
let satelliteTileLayer = L.layerGroup([
L.tileLayer.chinaProvider('TianDiTu.Satellite.Map', { key: MapKey }),
L.tileLayer.chinaProvider('TianDiTu.Satellite.Annotion', { key: MapKey })
]);
// 加载遥感图层
satelliteTileLayer.addTo(map.value);
layerGroup.addTo(map.value); // 将 layerGroup 添加到地图
});
return {
map,
layerGroup,
layerVisibility, // 添加到返回对象中
allLayers,
};
}
};
</script>
<style scoped>
.container {
display: flex;
flex-direction: column;
/* 垂直布局 */
justify-content: center;
/* 垂直居中 */
align-items: center;
/* 水平居中 */
height: 100vh;
/* 使容器占满整个视口高度 */
}
</style>