本篇介绍一下使用openlayers添加tips,比如测量时,实时显示长度、面积等,两种方法:
- 使用Overlay,会添加指定的HTML元素到dom中,会跟随地图的平移】缩放等动作
- 使用vector样式,会渲染到地图的canvas中,会跟随地图的平移】缩放等动作
推荐第2种,与地图无缝集成,实现简单
1 需求
- 基础多边形绘制,编辑功能
- 绘制编辑时跟随鼠标给出提示
- 多边形的边显示长度,中心显示面积
2 分析
主要是 openlayers 中 styleFunc 功能的使用,效果如下
3 实现
<template>
<div id="map" class="map"></div>
<div class="toolbar">
<el-button type="primary" @click="handleClick">{{ drawFlag ? '结束' : '开始' }}绘制</el-button>
<el-button type="warning" :disabled="!drawFlag" @click="handleCancelClick">撤销上步</el-button>
<el-button type="success" @click="handleShowDataClick(true)"
>{{ showLength ? '隐藏' : '显示' }}边长</el-button
>
<el-button type="success" @click="handleShowDataClick(false)"
>{{ showArea ? '隐藏' : '显示' }}面积</el-button
>
</div>
</template>
<script setup lang="ts">
import { Collection, Feature, Map, View } from 'ol';
import { get } from 'ol/proj';
import { RegularShape, Style } from 'ol/style';
import { Draw, Modify, Select, Snap } from 'ol/interaction.js';
import { Vector as VectorSource } from 'ol/source.js';
import { Vector as VectorLayer } from 'ol/layer.js';
import { Circle, Fill, Stroke, Text } from 'ol/style.js';
import { LineString, Point, Polygon } from 'ol/geom';
import { getArea, getLength } from 'ol/sphere.js';
const projection = get('EPSG:4326');
const source = new VectorSource({ wrapX: false });
const map = shallowRef();
const draw = shallowRef();
const vector = shallowRef();
const modify = shallowRef();
const select = shallowRef();
const snap = shallowRef();
const features = ref(new Collection()); //保存绘制的features
const activeFeatures = ref(new Collection()); //选中的features
const drawFlag = ref(false);
const showLength = ref(true);
const showArea = ref(true);
const tips = ref('');
const labelStyle = new Style({
text: new Text({
font: '14px Microsoft YaHei',
fill: new Fill({
color: 'rgba(255, 255, 255, 1)'
}),
backgroundFill: new Fill({
color: 'rgba(0, 0, 0, 0.7)'
}),
padding: [3, 3, 3, 3],
textBaseline: 'bottom',
offsetY: -15
}),
image: new RegularShape({
radius: 8,
points: 3,
angle: Math.PI,
displacement: [0, 10],
fill: new Fill({
color: 'rgba(0, 0, 0, 0.7)'
})
})
});
const tipStyle = new Style({
text: new Text({
font: '12px Microsoft YaHei',
fill: new Fill({
color: 'rgba(255, 255, 255, 1)'
}),
backgroundFill: new Fill({
color: 'rgba(0, 0, 0, 0.5)'
}),
padding: [2, 2, 2, 2],
textAlign: 'left',
offsetX: 15
})
});
const modifyStyle = new Style({
image: new Circle({
radius: 5,
stroke: new Stroke({
color: 'rgba(255, 255, 255, 0.4)'
}),
fill: new Fill({
color: 'rgba(255, 255, 255, 0.4)'
})
}),
text: new Text({
text: '拖动顶点进行修改',
font: '12px Microsoft YaHei',
fill: new Fill({
color: 'rgba(255, 255, 255, 1)'
}),
backgroundFill: new Fill({
color: 'rgba(0, 0, 0, 0.7)'
}),
padding: [5, 5, 5, 5],
textAlign: 'left',
offsetX: 15
})
});
const edgeStyle = new Style({
text: new Text({
font: '12px Microsoft YaHei',
fill: new Fill({
color: 'rgba(255, 255, 255, 1)'
}),
backgroundFill: new Fill({
color: 'rgba(0, 0, 0, 0.4)'
}),
padding: [2, 2, 2, 2],
textBaseline: 'bottom',
offsetY: -12
}),
image: new RegularShape({
radius: 6,
points: 3,
angle: Math.PI,
displacement: [0, 8],
fill: new Fill({
color: 'rgba(0, 0, 0, 0.4)'
})
})
});
onMounted(() => {
initMap();
initInteraction();
});
const initMap = () => {
vector.value = new VectorLayer({
source: source,
style: feature => styleFunc(feature, showLength.value, showArea.value)
});
map.value = new Map({
target: 'map',
view: new View({
center: [116.406393, 39.909006],
projection: projection,
zoom: 5
}),
layers: [vector.value]
});
};
const initInteraction = () => {
draw.value = new Draw({
active: false,
features: features.value,
source: source,
type: 'Polygon',
style: feature => drawStyleFunc(feature, showLength.value, showArea.value, tips.value)
});
draw.value.on('drawstart', e => {
console.log('e', e);
});
draw.value.on('drawend', e => {
console.log('features.value', features.value);
console.log('coord', e.feature.getGeometry().getCoordinates());
});
map.value.addInteraction(draw.value);
draw.value.setActive(false);
select.value = new Select({
features: activeFeatures.value,
style: selectStyleFunc
});
select.value.on('select', e => {
modify.value.setActive(true);
});
map.value.addInteraction(select.value);
select.value.setActive(false);
modify.value = new Modify({
features: select.value.getFeatures(), // Modify 将处理 Select 交互所选的要素
pixelTolerance: 10, //设置吸附像素值
insertVertexCondition: handleInsertVertexCondition,
style: feature => modifyStyleFunc(feature, showLength.value, showArea.value)
});
modify.value.on('modifystart', event => {});
modify.value.on('modifyend', event => {
console.log('modifyend', event.features);
});
map.value.addInteraction(modify.value);
modify.value.setActive(false);
// 用于处理绘制或者编辑多边形时,与已经存在图形的自动吸附,如下是只吸附顶点,不媳妇边
snap.value = new Snap({
source: source,
edge: false //不吸附边
});
map.value.addInteraction(snap.value);
};
const handleClick = () => {
drawFlag.value = !drawFlag.value;
draw.value.setActive(drawFlag.value);
select.value.setActive(!drawFlag.value);
modify.value.setActive(!drawFlag.value);
select.value.getFeatures().clear();
tips.value = '点击鼠标定位第一个顶点';
};
const handleCancelClick = () => {
draw.value.removeLastPoint();
};
const handleShowDataClick = isLength => {
if (isLength) {
showLength.value = !showLength.value;
} else {
showArea.value = !showArea.value;
}
draw.value.getOverlay().changed();
vector.value.changed();
};
const drawStyleFunc = (feature, showLength, showArea, tip) => {
const styles = [];
const geometry = feature.getGeometry();
const type = geometry.getType();
const coord = geometry.getCoordinates();
const tStyle = tipStyle.clone();
if (type === 'LineString') {
for (let i = 0; i < coord.length - 1; i++) {
const line = new LineString([coord[i], coord[i + 1]]);
const label = formatLength(line);
const point = new Point(line.getCoordinateAt(0.5));
const style = edgeStyle.clone();
style.setGeometry(point);
style.getText().setText(label);
if (showLength) {
styles.push(style);
}
styles.push(
new Style({
geometry: line,
stroke: new Stroke({
color: 'orange',
lineDash: coord.length > 2 && i < coord.length - 2 ? [] : [10],
width: 2
})
})
);
}
const point = new Point(geometry.getLastCoordinate());
tStyle.setGeometry(point);
if (coord.length <= 3) {
tips.value = '继续定位顶点';
} else if (coord.length > 3) {
tips.value = '点击第一个点可结束绘制';
}
}
if (type === 'Polygon') {
if (showArea) {
const label = formatArea(geometry);
const point = geometry.getInteriorPoint();
const style = labelStyle.clone();
// style.setGeometry(point);
style.getText().setText(label);
styles.push(style);
}
}
tStyle.getText().setText(tip);
styles.push(tStyle);
return styles;
};
const selectStyleFunc = feature => {
const styles = [];
const coord = feature.getGeometry().getCoordinates().flat(1);
for (let i = 0; i < coord.length - 1; i++) {
styles.push(
new Style({
geometry: new Point(coord[i]),
image: new Circle({
radius: 4,
fill: new Fill({
color: '#ffff'
}),
stroke: new Stroke({
color: 'orange',
width: 2
})
})
})
);
}
styles.push(
new Style({
stroke: new Stroke({
color: 'orange',
width: 2
}),
fill: new Fill({
color: '#ffff'
})
})
);
return styles;
};
const modifyStyleFunc = (feature, showLength, showArea) => {
const layer = map.value.getLayers().item(0);
const features = layer.getSource().getFeatures();
const geometry = feature.getGeometry();
const coord = geometry.getCoordinates();
const type = geometry.getType();
const coords = features.map(feature => feature.getGeometry().getCoordinates()).flat(2);
let styles = [];
// 只有鼠标在顶点时才能触发编辑功能
if (coords.find(c => c.toString() === coord.toString())) {
styles.push(
new Style({
geometry: new Point(coord),
image: new Circle({
radius: 6,
fill: new Fill({
color: '#ffff'
}),
stroke: new Stroke({
color: 'red',
width: 2
})
})
}),
modifyStyle
);
}
if (type === 'Point') {
const feature = activeFeatures.value.getArray()[0];
const geometry = feature.getGeometry();
const coord = feature.getGeometry().getCoordinates()[0];
if (showLength) {
for (let i = 0; i < coord.length - 1; i++) {
const line = new LineString([coord[i], coord[i + 1]]);
const label = formatLength(line);
const point = new Point(line.getCoordinateAt(0.5));
const style = edgeStyle.clone();
style.setGeometry(point);
style.getText().setText(label);
styles.push(style);
}
}
if (showArea) {
const label = formatArea(geometry);
const point = geometry.getInteriorPoint();
const style = labelStyle.clone();
style.setGeometry(point);
style.getText().setText(label);
styles.push(style);
}
}
return styles;
};
const handleInsertVertexCondition = e => {
// 点击或者拖动边时不插入新顶点
return false;
};
const styleFunc = (feature, showLength, showArea) => {
const styles = [];
const geometry = feature.getGeometry();
const type = geometry.getType();
if (type === 'Polygon') {
const coord = geometry.getCoordinates()[0];
if (showLength) {
for (let i = 0; i < coord.length - 1; i++) {
const line = new LineString([coord[i], coord[i + 1]]);
const label = formatLength(line);
const point = new Point(line.getCoordinateAt(0.5));
const style = edgeStyle.clone();
style.setGeometry(point);
style.getText().setText(label);
styles.push(style);
}
}
if (showArea) {
const label = formatArea(geometry);
const point = geometry.getInteriorPoint();
const style = labelStyle.clone();
style.setGeometry(point);
style.getText().setText(label);
styles.push(style);
}
}
styles.push(
new Style({
fill: new Fill({
color: [128, 128, 255, 0.5]
}),
stroke: new Stroke({
color: 'blue',
width: 2
})
})
);
return styles;
};
const formatArea = polygon => {
const area = getArea(polygon, { projection: 'EPSG:4326' });
let output; //根据面积大小实时换算成平方千米
if (area > 10000) {
//平方千米
output = Math.round((area / 1000000) * 100) / 100 + ' km\xB2';
} else {
//平方米
output = Math.round(area * 100) / 100 + ' m\xB2';
}
return output;
};
const formatLength = line => {
const length = getLength(line, { projection: 'EPSG:4326' });
let output; //根据面积大小实时换算成平方千米
if (length > 100) {
output = Math.round((length / 1000) * 100) / 100 + ' km';
} else {
output = Math.round(length * 100) / 100 + ' m';
}
return output;
};
</script>
<style scoped lang="scss">
.map {
width: 100%;
height: 100%;
background: #fff;
}
.toolbar {
position: absolute;
top: 20px;
left: 100px;
}
</style>
4 问题
- 跟随鼠标的tip背景会加深