一、技术栈
Vue 3 + Typescript +Openlayers 7 + Element plus + turf.js
二、组件代码
-
turf.js介绍
Turf.js是MapBox公司研发的基于浏览器端的空间分析库,它使用JavaScript进行编写,通过npm进行包管理。值得一提的是,良好的模块化设计使其不仅能够作用于浏览器端、还可通过Node.js在服务端使用。Turf 原生支持 GeoJSON 矢量数据。GeoJSON 的优点是结构简单,并且得到了所有网页地图API的支持;但 GeoJSON 不支持空间索引,这个缺点可能会限制 Turf 处理大型文件的能力效率。其适用于轻量级(数据轻量而非功能轻量)的WebGIS应用。
浏览器端支持空间分析的意义在于,通过网页地图的不仅可提供地名搜索与路径查询(目前 Google Maps 的功能其实与十年前并没有太大区别),而且可以在浏览器中分享空间分析模型。以前的 WebGIS 功能当然也支持空间分析,但是分析过程需要在服务器端进行,本地能够进行的设置有限,现在使用 Turf.js 可以将分析过程完全移到本地,如果页面中提供了参数设置的话,可以在本地对模型进行修改并立即看到分析结果。这样的直接好处有两个方面:更直接的数据展示,以及更加复杂的用户交互(复杂交互本身需要空间分析作为基础)。
Turf.js处理数据默认为WGS 84地理坐标系,需要注意坐标系转换,避免影响计算结果。
-
OLAnalyst.vue
<template>
<div class="analystDiv">
<el-button-group style="margin-right: 10px">
<el-button @click="loadingBuffdata">加载缓冲数据</el-button>
<el-button @click="bufferAnalyst">缓冲区分析</el-button>
</el-button-group>
<OLMeasure></OLMeasure>
<el-button @click="polygonCentroid">多边形质心</el-button>
</div>
</template>
<script setup lang="ts">
import OLMeasure from "./OLMeasure.vue";
import { Map } from "ol";
import bus from "@/utils/bus.ts";
import VectorSource from "ol/source/Vector";
import VectorLayer from "ol/layer/Vector";
import { Style, Stroke, Fill } from "ol/style";
import { LineString, Point, Polygon } from "ol/geom";
import { Feature } from "ol";
import { fromLonLat } from "ol/proj";
import * as turf from "@turf/turf";
import { Position } from "@turf/turf";
import CircleStyle from "ol/style/Circle.js";
import { Coordinate } from "ol/coordinate";
//监听获取map数据
let mapCopy: Map;
bus.on("mapToChecked", (res: Map) => {
// 传参由回调函数中的形参接受
mapCopy = res;
});
//点样式
const pointStyle: Style = new Style({
image: new CircleStyle({
// 点半径
radius: 8,
// 点的边框,
stroke: new Stroke({
color: "green",
width: 2,
}),
// 缩放比
scale: 1,
// 填充色
fill: new Fill({
color: "yellow",
}),
}),
});
//线样式
const lineStyle: Style = new Style({
stroke: new Stroke({
color: "rgba(22, 212, 107, 0.8)",
width: 4,
lineCap: "round",
lineJoin: "round",
}),
});
//面样式
const pologyStyle: Style = new Style({
fill: new Fill({
color: "rgba(86, 182, 178, 0.2)",
}),
stroke: new Stroke({
color: "#dd4c35",
width: 2,
}),
});
//创建分析要素:点、线、面
const pointFeature: Feature<Point> = new Feature({
geometry: new Point([112.994818, 28.131583]),
name: "point",
});
pointFeature.setStyle(pointStyle);
const lineFeature: Feature<LineString> = new Feature({
geometry: new LineString([
[112.996953, 28.134545],
[112.998401, 28.132312],
]),
name: "line",
});
lineFeature.setStyle(lineStyle);
const polygonFeature: Feature<Polygon> = new Feature({
//创建要素的时候不要进行投影,保持其坐标系为4326
geometry: new Polygon([
[
[112.990784, 28.131243],
[112.989711, 28.128897],
[112.992662, 28.129332],
[112.990784, 28.131243],
],
]),
name: "polygon",
});
polygonFeature.setStyle(pologyStyle);
// 创建分析要素数组
const featureArr: Feature<Point>[] = [
lineFeature,
polygonFeature,
pointFeature,
] as Feature<Point>[]; //放在外部定义接收,避免类型判断
const analystVectorSource = new VectorSource({
//将创建好的要素加载进数据源
features: featureArr,
});
// 缓冲区分析,要在数据源加载之前进行缓冲区分析,不然后面加载图层之后需要进行坐标转换,会导致二次坐标转换而图层扭曲
const bufferRadius = 100; //缓冲区半径
let bufferFeatures: Coordinate[] = []; //定义一个暂时接收缓冲区要素坐标的数组
const bufferFeature: Coordinate[][] = []; //定义一个接收生成的缓冲区要素坐标的二维数组
analystVectorSource.getFeatures().forEach((feature, index) => {
//循环遍历数据源,对每一个要素进行处理
bufferFeatures = []; //每次都先置空临时数组
const geometry = feature.getGeometry(); //获取要素上的几何
const geometryType = geometry?.getType(); //获取几何类型
const geomCoordinates = geometry?.getCoordinates(); //获取几何的坐标
let turfFeature;
switch (
geometryType //根据ol里的几何的坐标和类型,生成turf里的Feature
) {
case "Point":
turfFeature = turf.point(geomCoordinates as Position);
break;
case "LineString":
turfFeature = turf.lineString(geomCoordinates as unknown as Position[]);
break;
case "Polygon":
turfFeature = turf.polygon(geomCoordinates as unknown as Position[][]);
break;
}
const bufferGeometry = turf.buffer(
turfFeature as
| turf.helpers.Feature<turf.helpers.Point, turf.helpers.Properties>
| turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties>,
bufferRadius,
{
units: "meters",
}
); //对turf里的Feature进行缓冲区分析,turf的处理默认在坐标系EPSG:4326,所以分析要在加载数据源之前完成
const bufferPolygonCoordinates:
| turf.helpers.Position[]
| turf.helpers.Position[][] = bufferGeometry.geometry.coordinates[0]; //生成的缓冲区多边形的坐标
for (let i = 0, l = bufferPolygonCoordinates.length; i < l; i++) {
//遍历缓冲区多边形坐标
bufferFeatures.push(
fromLonLat(
[
//通过fromLonLat进行坐标转换
bufferPolygonCoordinates[i][0] as unknown as number, //类型断言
bufferPolygonCoordinates[i][1] as unknown as number,
],
"EPSG:4548" //将缓冲区多边形的坐标转换为我们需要的ol的4548坐标系下的坐标
)
);
}
bufferFeature[index] = bufferFeatures; //将临时数据存入二维数组
});
if (analystVectorSource.getState() === "ready") {
//因为地图实例坐标为4548,数据源坐标为4326,直接对数据源进行遍历转换
const features = analystVectorSource.getFeatures();
for (let i = 0, l = features.length; i < l; i++) {
let geometry = features[i].getGeometry();
geometry?.transform("EPSG:4326", "EPSG:4548");
}
}
//加载缓冲区要素、分析要素图层
const analystVectorLayer = new VectorLayer({
//将坐标变换后的数据源加载到图层中
source: analystVectorSource,
properties: {
title: "analystLayer",
name: "分析要素图层",
type: "VectorLayer",
},
});
//创建缓冲区要素图层
const buffVectorLayer = new VectorLayer({
source: new VectorSource({
features: [
new Feature({
geometry: new Polygon(bufferFeature), //根据turf生成的缓冲区多边形转换后的坐标生成ol多边形
}),
],
}),
style: new Style({
//缓冲区多边形样式
fill: new Fill({
color: "rgba(76, 209, 224, 0.2)",
}),
stroke: new Stroke({
color: "red",
width: 2,
}),
}),
properties: {
title: "buffLayer",
name: "缓冲区图层",
type: "VectorLayer",
},
});
const loadingBuffdata = () => {
mapCopy.addLayer(analystVectorLayer);
};
const bufferAnalyst = () => {
mapCopy.addLayer(buffVectorLayer);
};
//多边形质心计算
const polygonCentroid = () => {
const cpo = turf.polygon([
[
[112.990784, 28.131243],
[112.989711, 28.128897],
[112.992662, 28.129332],
[112.990784, 28.131243],
],
]); //生成turf多边形
const centroid = turf.centroid(cpo); //计算质心坐标(基于4326)
const centroidCoordinates = fromLonLat(
[centroid.geometry.coordinates[0], centroid.geometry.coordinates[1]],
"EPSG:4548"
); //坐标系转换为4548
const centroidVectorLayer = new VectorLayer({
//创建质心图层
source: new VectorSource({
features: [
new Feature({
geometry: new Point(centroidCoordinates),
}),
],
}),
properties: {
title: "centroidLayer",
name: "质心图层",
type: "VectorLayer",
},
});
mapCopy.addLayer(centroidVectorLayer); //添加质心图层
};
</script>
<style>
.analystDiv {
display: flex;
}
</style>