测量
TIP
单击开始绘图。左下角可以选择几何体的类型和是否使用测地线的措施。
Vue 代码如下:
点我查看代码
vue
<template>
<div id="map">
<div id="menu">
几何体类型
<el-select
v-model="state.typeSelect"
placeholder="Select"
size="small"
@change="handleChange"
class="select"
>
<el-option
v-for="item in state.options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-checkbox
v-model="state.checked"
label="使用测地线的措施"
size="large"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, reactive, onBeforeUnmount } from "vue";
import { Map, View, Overlay, Feature } from "ol";
import { Coordinate } from "ol/coordinate";
import { Tile as TileLayer } from "ol/layer";
import { Vector } from "ol/source";
import { Polygon, LineString, Geometry } from "ol/geom";
import { unByKey } from "ol/Observable";
import { MAPURL, ATTRIBUTIONS } from "../../../constants";
import { Draw } from "ol/interaction";
import { DrawEvent } from "ol/interaction/Draw";
import { EventsKey } from "ol/events";
import XYZ from "ol/source/XYZ";
import {
createHelpTooltip,
createMeasureTooltip,
formatLength,
formatArea,
drawGeometricFigure,
createVector,
} from "./index";
interface State {
checked: boolean;
typeSelect: string;
options: {
label: string;
value: string;
}[];
map: null | Map;
sketch: null | Feature;
helpTooltipElement: null | HTMLElement;
helpTooltip: null | Overlay;
measureTooltipElement: null | HTMLElement;
measureTooltip: null | Overlay;
draw: null | Draw;
}
const raster = new TileLayer({
source: new XYZ({
attributions: ATTRIBUTIONS,
url: MAPURL,
maxZoom: 20,
}),
});
/**
* 当用户正在绘制多边形时的提示信息文本
* @type {string}
*/
const continuePolygonMsg = "单击继续绘制多边形";
/**
* 当用户正在绘制线时的提示信息文本
* @type {string}
*/
const source = new Vector();
const continueLineMsg = "单击继续绘制直线";
const state: State = reactive({
checked: false,
typeSelect: "length",
options: [
{
label: "长度",
value: "length",
},
{
label: "面积",
value: "area",
},
],
map: null,
sketch: null, // 当前绘制的要素
helpTooltipElement: null, // 帮助提示框对象
helpTooltip: null, // 帮助提示框显示的信息
measureTooltipElement: null, // 测量工具提示框对象
measureTooltip: null, // 测量工具中显示的测量值
draw: null,
});
//初始化map
const initMap = () => {
state.map = new Map({
target: "map",
//地图容器中加载的图层
layers: [
//加载瓦片图层数据
raster,
],
view: new View({
projection: "EPSG:4326", // 坐标系,有EPSG:4326和EPSG:3 857
center: [0, 0],
//地图初始显示级别
zoom: 5,
}),
});
};
// 加载测量的绘制矢量层
const loadVector = () => {
if (state.map === null) return;
// 加载测量的绘制矢量层
const vector = createVector(source);
state.map.addLayer(vector);
};
/**
* 鼠标移动事件处理函数
* @param {ol.MapBrowserEvent} evt
*/
const pointerMoveHandler = (evt) => {
if (evt.dragging) {
return;
}
/** @type {string} */
let helpMsg = "单击开始绘图"; //当前默认提示信息
//判断绘制几何类型设置相应的帮助提示信息
if (state.sketch) {
const getGeometry = state.sketch.getGeometry();
if (getGeometry instanceof Polygon) {
helpMsg = continuePolygonMsg; //绘制多边形时提示相应内容
} else if (getGeometry instanceof LineString) {
helpMsg = continueLineMsg; //绘制线时提示相应内容
}
}
if (state.helpTooltipElement === null || state.helpTooltip === null) return;
state.helpTooltipElement.innerHTML = helpMsg; //将提示信息设置到对话框中显示
state.helpTooltip.setPosition(evt.coordinate); //设置帮助提示框的位置
state.helpTooltipElement.classList.remove("hidden");
};
/**
* 加载交互绘制控件函数
*/
const addInteraction = () => {
if (state.map === null) return;
const type = state.typeSelect === "area" ? "Polygon" : "LineString";
state.draw = drawGeometricFigure({ source, type });
state.map.addInteraction(state.draw);
//创建测量工具提示框
[state.measureTooltipElement, state.measureTooltip] = createMeasureTooltip({
measureTooltipElement: state.measureTooltipElement,
map: state.map,
});
//创建帮助提示框
[state.helpTooltipElement, state.helpTooltip] = createHelpTooltip({
helpTooltipElement: state.helpTooltipElement,
map: state.map,
});
let listener: EventsKey;
//绑定交互绘制工具开始绘制的事件
state.draw.on("drawstart", function (evt: DrawEvent) {
state.sketch = evt.feature; //绘制的要素
let tooltipCoord: Coordinate; // 绘制的坐标
//绑定change事件,根据绘制几何类型得到测量长度值或面积值,并将其设置到测量工具提示框中显示
const getGeometry = state.sketch.getGeometry();
if (getGeometry) {
listener = getGeometry.on("change", function (evt) {
if (state.map === null) return;
const geom: Geometry = evt.target; //绘制几何要素
let output: string = "";
if (geom instanceof Polygon) {
output = formatArea({
checked: state.checked,
polygon: geom,
map: state.map,
}); //面积值
tooltipCoord = geom.getInteriorPoint().getCoordinates(); //坐标
} else if (geom instanceof LineString) {
output = formatLength({
checked: state.checked,
line: geom,
map: state.map,
}); //长度值
tooltipCoord = geom.getLastCoordinate(); //坐标
}
if (state.measureTooltipElement) {
state.measureTooltipElement.innerHTML = output; //将测量值设置到测量工具提示框中显示
}
if (state.measureTooltip) {
state.measureTooltip.setPosition(tooltipCoord); //设置测量工具提示框的显示位置
}
});
}
});
//绑定交互绘制工具结束绘制的事件
state.draw.on("drawend", function () {
if (
state.measureTooltipElement !== null &&
state.measureTooltip !== null &&
state.map !== null
) {
state.measureTooltipElement.className = "tooltip tooltip-static"; //设置测量提示框的样式
state.measureTooltip.setOffset([0, -7]);
// 未设置的草图
state.sketch = null; //置空当前绘制的要素对象
// 取消设置工具提示,以便创建新的工具提示
state.measureTooltipElement = null; //置空测量工具提示框对象
//重新创建一个测试工具提示框显示结果
[state.measureTooltipElement, state.measureTooltip] =
createMeasureTooltip({
measureTooltipElement: state.measureTooltipElement,
map: state.map,
});
unByKey(listener);
}
});
};
// 让用户切换选择测量类型(长度/面积)
const handleChange = () => {
if (state.map === null || state.draw === null) return;
state.map.removeInteraction(state.draw); //移除绘制图形
addInteraction(); //添加绘图进行测量
};
onMounted(() => {
initMap();
loadVector();
if (state.map) {
state.map.on("pointermove", pointerMoveHandler); //地图容器绑定鼠标移动事件,动态显示帮助提示框内容
//地图绑定鼠标移出事件,鼠标移出时为帮助提示框设置隐藏样式
state.map.getViewport().addEventListener("mouseout", () => {
if (state.helpTooltipElement) {
state.helpTooltipElement.classList.add("hidden");
}
});
}
addInteraction(); //调用加载绘制交互控件方法,添加绘图进行测量
});
onBeforeUnmount(() => {
if (state.map) {
state.map.dispose();
state.map = null;
}
});
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#map {
position: relative;
width: 100%;
height: 650px;
}
#menu {
width: 450px;
position: absolute;
left: 20px;
bottom: 20px;
color: #fff;
font-size: 16px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 1;
}
/**
* 提示框的样式信息
*/
:deep(.tooltip) {
position: relative;
background: rgba(0, 0, 0, 0.5);
border-radius: 4px;
color: white;
padding: 4px 8px;
opacity: 0.7;
white-space: nowrap;
}
:deep(.tooltip-measure) {
opacity: 1;
font-weight: bold;
}
:deep(.tooltip-static) {
background-color: #ffcc33;
color: black;
border: 1px solid white;
}
:deep(.tooltip-measure:before),
:deep(.tooltip-static:before) {
border-top: 6px solid rgba(0, 0, 0, 0.5);
border-right: 6px solid transparent;
border-left: 6px solid transparent;
content: "";
position: absolute;
bottom: -6px;
margin-left: -7px;
left: 50%;
}
:deep(.tooltip-static:before) {
border-top-color: #ffcc33;
}
</style>
index.ts 代码如下:
点我查看代码
ts
import { Overlay, Map } from "ol";
import { LineString, Polygon } from 'ol/geom'
import VectorSource from 'ol/source/Vector'
import { Type } from 'ol/geom/Geometry'
import { getLength, getArea } from "ol/sphere";
import { Style, Fill, Stroke, Circle } from "ol/style";
import { Draw } from "ol/interaction";
import { Vector } from "ol/layer"
/**
* 创建一个新的帮助提示框(tooltip)
* @param {Element} helpTooltipElement 帮助提示框对象
* @param {ol.Map} map 地图实例
*/
export function createHelpTooltip({ helpTooltipElement, map }: { helpTooltipElement: HTMLElement | null, map: Map }): [HTMLDivElement, Overlay] {
if (helpTooltipElement && helpTooltipElement.parentNode) {
helpTooltipElement.parentNode.removeChild(helpTooltipElement);
}
const newHelpTooltipElement = document.createElement("div");
newHelpTooltipElement.className = "tooltip hidden";
const helpTooltip = new Overlay({
element: newHelpTooltipElement,
offset: [15, 0],
positioning: "center-left",
});
map.addOverlay(helpTooltip);
return [newHelpTooltipElement, helpTooltip]
}
/**
* 创建一个新的测量工具提示框(tooltip)
* @param {Element} measureTooltipElement 测量工具提示框对象
* @param {ol.Map} map 地图实例
*/
export function createMeasureTooltip({ measureTooltipElement, map }: { measureTooltipElement: HTMLElement | null, map: Map }): [HTMLDivElement, Overlay] {
if (measureTooltipElement && measureTooltipElement.parentNode) {
measureTooltipElement.parentNode.removeChild(measureTooltipElement);
}
const newMeasureTooltipElement = document.createElement("div");
newMeasureTooltipElement.className = "tooltip tooltip-measure";
const measureTooltip = new Overlay({
element: newMeasureTooltipElement,
offset: [0, -15],
positioning: "bottom-center",
});
map.addOverlay(measureTooltip);
return [newMeasureTooltipElement, measureTooltip]
}
/**
* 测量长度输出
* @param {boolean} checked 是否使用测地学方法测量
* @param {ol.geom.LineString} line
* @param {ol.Map} map 地图实例
* @return {string}
*/
export function formatLength({ checked, line, map }: { checked: boolean, line: LineString, map: Map }): string {
let length: number;
if (checked) {
//若使用测地学方法测量
const sourceProj = map.getView().getProjection(); //地图数据源投影坐标系
length = getLength(line, {
projection: sourceProj,
radius: 6378137,
});
} else {
length = Math.round(line.getLength() * 100) / 100; //直接得到线的长度
}
let output: string;
if (length > 100) {
output = Math.round((length / 1000) * 100) / 100 + " " + "km"; //换算成KM单位
} else {
output = Math.round(length * 100) / 100 + " " + "m"; //m为单位
}
return output; //返回线的长度
}
/**
* 测量面积输出
* @param {boolean} checked 是否使用测地学方法测量
* @param {ol.geom.Polygon} polygon
* @param {ol.Map} map 地图实例
* @return {string}
*/
export function formatArea({ checked, polygon, map }: { checked: boolean, polygon: Polygon, map: Map }): string {
let area: number;
if (checked) {
//若使用测地学方法测量
const sourceProj = map.getView().getProjection(); //地图数据源投影坐标系
const geom = /** @type {ol.geom.Polygon} */ (
polygon.clone().transform(sourceProj, "EPSG:4326")
); //将多边形要素坐标系投影为EPSG:4326
area = Math.abs(
getArea(geom, { projection: sourceProj, radius: 6378137 })
); //获取面积
} else {
area = polygon.getArea(); //直接获取多边形的面积
}
let output: string;
if (area > 10000) {
output =
Math.round((area / 1000000) * 100) / 100 + " " + "km<sup>2</sup>"; //换算成KM单位
} else {
output = Math.round(area * 100) / 100 + " " + "m<sup>2</sup>"; //m为单位
}
return output; //返回多边形的面积
}
/**
* 绘制几何图形
* @param {ol.source.Vector} source 测量绘制层数据源
* @param {ol.geom.GeometryType} type 几何图形类型
* @return 返回绘制好的几何图形
*/
export function drawGeometricFigure({ source, type }: { source: VectorSource, type: Type }): Draw {
return new Draw({
source, //测量绘制层数据源
type,
style: new Style({
//绘制几何图形的样式
fill: new Fill({
color: "rgba(255, 255, 255, 0.2)",
}),
stroke: new Stroke({
color: "rgba(0, 0, 0, 0.5)",
lineDash: [10, 10],
width: 2,
}),
image: new Circle({
radius: 5,
stroke: new Stroke({
color: "rgba(0, 0, 0, 0.7)",
}),
fill: new Fill({
color: "rgba(255, 255, 255, 0.2)",
}),
}),
}),
});
}
// 创建测量的绘制矢量层
export function createVector(source: VectorSource): Vector<VectorSource> {
return new Vector({
source,
style: new Style({
//图层样式
fill: new Fill({
color: "rgba(255, 255, 255, 0.2)", //填充颜色
}),
stroke: new Stroke({
color: "#ffcc33", //边框颜色
width: 2, // 边框宽度
}),
image: new Circle({
radius: 7,
fill: new Fill({
color: "#ffcc33",
}),
}),
}),
})
}