使用技术:vue3.2+openlayers8.2.0
实现功能:
点击绘制图形按钮,从缓存中获取图形绘制到地图上(实际项目中可替换成请求接口获取图形数据绘制到地图)
点击多边形、圆形、三角形...后可在地图上绘制对应图形(实际项目中可替换成下拉框+数字input框),绘制完的图形默认打开编辑绘制
点击清除绘制按钮,会将地图矢量绘制层矢量数据源全部清除
点击关闭绘制按钮,如果当前处于编辑状态,则关闭编辑状态
点击编辑绘制按钮,可以选择当前地图上显示的图形,然后编辑图形对象
<template>
<div style="width: 100%;height: 100%">
<div class="map" id="mymap"></div>
<div class="tools shadow-md rounded flex">
<el-button class="btn-search" @click="drawSketch" type="success">绘制图形</el-button>
<el-button class="btn-search" @click="handleAddInteraction('Polygon')" type="primary">多边形</el-button>
<el-button class="btn-search" @click="handleAddInteraction('Circle')" type="primary">圆形</el-button>
<el-button class="btn-search" @click="handleAddInteraction('LineString')" type="primary">线条</el-button>
<el-button class="btn-search" @click="handleAddInteraction('Circle', 3)" type="primary">三角形</el-button>
<el-button class="btn-search" @click="handleAddInteraction('Circle', 6)" type="primary">六边形</el-button>
<el-button class="btn-search" @click="clearDraw" type="danger">清除绘制</el-button>
<el-button class="btn-search" @click="closeDraw" v-show="isOpenModify == 1" type="danger">关闭绘制</el-button>
<el-button class="btn-search" @click="openDraw" v-show="isOpenModify == 0" type="danger">编辑绘制</el-button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import 'ol/css';
import Map from 'ol/Map';
import VectorSource from 'ol/source/Vector';
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer';
import View from 'ol/View';
import Point from 'ol/geom/Point';
import { getArea, getLength } from 'ol/sphere';
import LineString from 'ol/geom/LineString';
import Polygon from 'ol/geom/Polygon';
import Circle from 'ol/geom/Circle';
import Feature from 'ol/Feature';
import { Draw, Select, Modify } from 'ol/interaction';
import { createRegularPolygon } from 'ol/interaction/Draw';
import XYZ from 'ol/source/XYZ'
import { Style, Fill, Stroke, Circle as Cir, Icon, Text } from 'ol/style';
import markerImg from '@/assets/img/marker.png';
/**
* mapDom:地图容器
* mapObj:地图对象
* source_draw 用于绘制形状的矢量地图源
* drawLayer 矢量绘制层
* draw_inter 绘制形状的交互对象
* select_f 选择形状的交互对象
* modify_f 编辑形状的交互对象
* sketch 当前绘制对象(feature)
* popup_overlay 弹窗overlay,绘制完成后展示编号
* layer_poly 用于展示多边形的layer层
* makers 点列表
* pointList 点Feature对象列表
* pointLayer 点图层
* pointLayerSource 点图层数据源
*/
const mapDom = ref(null)
const mapObj = ref(null)
const source_draw = ref(null)
const drawLayer = ref(null)
const draw_inter = ref(null)
const modify_f = ref(null)
const select_f = ref(null)
const sketch = ref(null)
const makers = ref([])
const pointList = ref([])
const pointLayer = ref(null)
const pointLayerSource = ref(null)
// const popup_overlay = ref(null)
// const layer_poly = ref(null)
const value_draw = ref('Polygon')
// 清除地图 某些情况 地图容器会存在两个 导致地图无法正常显示
const mapClear = () => {
if (mapDom.value) {
mapDom.value.innerHTML = ''
mapDom.value = null
}
}
mapClear()
//实例化一个矢量图层Vector作为绘制层
source_draw.value = new VectorSource()
drawLayer.value = new VectorLayer({
source: new VectorSource(),
style: new Style({
fill: new Fill({
color: 'rgba(68, 171, 218, 0.6)'
}),
stroke: new Stroke({
color: '#409EFF',
width: 2
}),
image: new Cir({
radius: 4,
fill: new Fill({
color: '#ffcc33'
})
})
}),
// zIndex: 98
})
// 添加一个使用离线瓦片地图的层
const offlineMapLayer = new TileLayer({
source: new XYZ({
// url: 'http://192.168.10.179:9527/roadmap/roadmap' + '/{z}/{x}/{y}.png' // 设置本地离线瓦片所在路径
url: 'https://wprd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}' // 在线高德地图
})
})
// 初始化地图
const initMap = () => {
// 先尝试清除
// 获取地图容器
mapDom.value = document.getElementById('mymap')
// 初始化地图配置
mapObj.value = new Map({
target: mapDom.value, // 地图容器
layers: [offlineMapLayer],
view: new View({
center: [120.990969, 31.635013], // 地图中心点
zoom: 10, // 缩放
minZoom: 3, // 最小缩放
maxZoom: 17, // 最大缩放
projection: 'EPSG:4326' // 坐标系
})
})
// 将图层添加到地图
mapObj.value.addLayer(drawLayer.value)
}
/**
* switch (type) {
case 'Point': 点
case 'MultiPoint': 多点
case 'LineString': 线
case 'MultiLineString': 多线
case 'Polygon': 多边形
case 'MultiPolygon': 多多边形
case 'Circle': 圆
}
*/
// 绘画交互图形
const handleAddInteraction = (type, num) => {
// 清除上一次的绘画
clearDraw()
// 如果当前选择了绘画,则清除,避免点击两种绘制方式时,出现两个绘画
if (draw_inter.value) {
draw_inter.value.setActive(false)
draw_inter.value.setMap(null)
draw_inter.value = null
}
draw_inter.value = new Draw({
source: source_draw.value,
type: type,
style: new Style({
fill: new Fill({ //填充样式
// color: 'rgba(68, 171, 218, 0.6)' // 蓝色
color: 'rgba(119,239,202, 0.6)' // 绿色
}),
stroke: new Stroke({ //线样式
color: '#409EFF',
width: 2
}),
image: new Cir({ //点样式
radius: 4,
fill: new Fill({
color: '#2d98da'
})
}),
}),
geometryFunction: num ? new createRegularPolygon(num) : false // 创建正多边形
});
//将交互绘图对象添加到地图中
mapObj.value.addInteraction(draw_inter.value)
//绘画开始时
// draw_inter.value.on('drawstart', (evt) => {
// sketch.value = evt.feature;
// })
//监听绘制结束事件
draw_inter.value.on('drawend', (evt) => {
sketch.value = evt.feature
let geometry = evt.feature.getGeometry();
handleDrawModify(geometry) // 处理绘制数据
// console.log(sketch.value.getGeometry().getCoordinates(), '------');
// 保存绘制的图形
localStorage.setItem('dataSketch', JSON.stringify(sketch.value.getProperties()))
// 添加到要素图层中
drawLayer.value.getSource().addFeature(sketch.value)
// 关闭绘制交互
mapObj.value.removeInteraction(draw_inter.value)
isOpenModify.value = 1
selectModify()
})
}
// 选中修改几何图形
const selectModify = () => {
select_f.value = new Select({
multi: false //取消多选
})
mapObj.value.addInteraction(select_f.value);
modify_f.value = new Modify({
features: select_f.value.getFeatures(), // 将选中的要素添加修改功能
style: new Style({
fill: new Fill({
color: 'rgba(245,108,108, 1)'
}),
stroke: new Stroke({
color: '#F56C6C',
width: 4
}),
image: new Cir({
radius: 6,
fill: new Fill({
color: '#F56C6C'
})
})
})
})
mapObj.value.addInteraction(modify_f.value)
select_f.value.on("select", function (evt) {
if (value_draw.value != "Polygon") {
//选中一个要素时
if (evt.selected.length == 1) {
sketch.value = evt.selected[0]
}
}
})
//监听要素修改时
modify_f.value.on("modifyend", function (evt) {
let new_feature = evt.features.item(0)
console.log(new_feature.getGeometry().getType(), 'newfeature--------');
if (new_feature) {
let geometry = new_feature.getGeometry();
if (geometry.getType() != 'Point') {
sketch.value = new_feature
handleDrawModify(geometry)
// 保存绘制的图形
localStorage.setItem('dataSketch', JSON.stringify(sketch.value.getProperties()))
}
}
})
}
// 处理绘制/修改交互图形
const handleDrawModify = (geometry) => {
// 获取绘制的几何图形
let geoType = geometry.getType();
let geoLength, area;
if (geoType == 'LineString') {
geoLength = formatLength(geometry)
} else {
area = formatArea(geometry)
}
//为sketch生成唯一编号
let date = new Date()
let dk_id = date.getFullYear().toString() + (date.getMonth() + 1).toString() +
date.getDate().toString() + date.getHours().toString() + date.getMinutes().toString()
+ date.getSeconds().toString()
//画完之后给sketch添加属性
sketch.value.setProperties({
attribute: {
dk_id,
state: 1,
type: geoType,
circleRadius: geoType == 'Circle' ? geometry.getRadius() : null, // 圆形半径
geoLength: geoType == 'LineString' ? geoLength : null, // 线段长度
area: geoType != 'LineString' ? area : null, // 图形面积
},
})
}
// 计算图形面积
const formatArea = (geometry) => {
let area
if (geometry.getType() == 'Polygon') {
area = getArea(geometry, { projection: 'EPSG:4326' })
} else {
/**
* 1度纬度在地球表面大约等于111千米(或111,000米)
* 面积 = π × (半径)^2
* = π × (555,000米)^2
* = π × 308,025,000,000平方米
* ≈ 969,646,240,000平方米
* 除以1,000,000:面积 ≈ 969,646平方千米
*/
let radius = geometry.getRadius()
area = Math.PI * Math.pow(radius * 111000, 2)
}
let output
if (area > 10000) {
output = Math.round((area / 1000000) * 100) / 100 + ' ' + 'km²'
} else {
output = Math.round(area * 100) / 100 + ' ' + 'm²'
}
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
}
// 请求数据并绘制
const drawSketch = async () => {
// 清除图层
clearDraw()
// 判断是否开启交互
if (draw_inter.value) {
// 关闭绘制交互
mapObj.value.removeInteraction(draw_inter.value)
}
// 获取缓存中的数据
let feature = localStorage.getItem('dataSketch') ? JSON.parse(localStorage.getItem('dataSketch')).geometry.flatCoordinates : null
if (!feature) {
return alert('没有图形数据')
}
let points = []
for (let i = 0; i < feature.length; i += 2) {
points.push([feature[i], feature[i + 1]]);
}
// 判断获取到的数据是什么图形
let dataSketch = JSON.parse(localStorage.getItem('dataSketch'))
let { dk_id, type, circleRadius, area, name, } = dataSketch.attribute
if (type === 'Polygon') {
await drawLayer.value.getSource().addFeature(new Feature({ geometry: new Polygon([points]) }))
let centerPoint = await calculateCenter(points)
makers.value = [{ longitude: centerPoint.centerX / points.length, latitude: centerPoint.centerY / points.length, label: name ?? dk_id, area }]
} else if (type === 'LineString') {
await drawLayer.value.getSource().addFeature(new Feature({ geometry: new LineString(points) }))
makers.value = [{ longitude: points[0][0], latitude: points[0][1], label: name ?? dk_id }]
} else if (type === 'Circle') {
await drawLayer.value.getSource().addFeature(new Feature({
geometry: new Circle(points[0], circleRadius)
}))
makers.value = [{
longitude: points[0][0],
latitude: points[0][1],
label: name ?? dk_id,
area
}]
}
// 添加标注点
addPoint()
let animateTime = 1500
// 使用View的animate方法来实现动画
mapObj.value.getView().animate({
center: [makers.value[0].longitude, makers.value[0].latitude],
zoom: 15,
duration: animateTime
});
}
// 计算多边形中心点坐标
const calculateCenter = async (points) => {
let centerX = 0, centerY = 0
await points.forEach((point) => {
centerX += point[0]
centerY += point[1]
})
return { centerX, centerY }
}
// 添加标注点位
const addPoint = () => {
pointList.value = []
// 地理坐标数组
makers.value.map(item => {
// 创建点
const point = new Feature({
geometry: new Point([item.longitude, item.latitude]),
data: item.label
})
// 点的样式
const iconStyle = new Style({
image: new Icon({
// color: 'red',
crossOrigin: 'anonymous',
// 设置图标
src: markerImg,
// 设置图标缩放
scale: 0.2,
// 设置图标偏移
anchor: [0.5, 0.5],
}),
text: new Text({
text: item.label + ' <' + item.area + '>',
cursor: 'pointer',
fill: new Fill({
color: '#000'
}),
backgroundFill: new Fill({
color: '#00E400'
}),
offsetX: 0,
offsetY: -25,
font: 'normal 14px 微软雅黑',
stroke: new Stroke({
color: '#00E400',
width: 2
})
}),
zIndex: 99
})
// 设置样式
point.setStyle(iconStyle)
// 保存到数据 方便删除
pointList.value.push(point)
})
// 创建geojson据源
pointLayerSource.value = new VectorSource({ features: pointList.value })
// 创建图层 并加载数据
pointLayer.value = new VectorLayer({ source: pointLayerSource.value })
// 将图层添加地图上
mapObj.value.addLayer(pointLayer.value)
}
onMounted(() => {
initMap()
})
// 清除绘制内容
const clearDraw = () => {
if (drawLayer.value) {
drawLayer.value.getSource().removeFeature()
drawLayer.value.getSource().clear()
}
if (pointLayer.value) {
mapObj.value.removeLayer(pointLayer.value)
pointLayer.value.getSource().removeFeature()
pointLayer.value.getSource().clear()
}
}
const isOpenModify = ref(-1)
// 开启修改交互
const openDraw = () => {
isOpenModify.value = 1
selectModify()
}
// 关闭绘制交互
const closeDraw = () => {
// 关闭绘制交互
if (modify_f.value) {
isOpenModify.value = 0
modify_f.value.setActive(false) // 关闭修改交互
mapObj.value.removeInteraction(select_f.value) // 关闭选择交互
}
}
</script>
<style scoped>
.map {
width: 100vw;
height: 98vh;
background: transparent;
}
.tools {
position: fixed;
z-index: 999;
top: 60px;
right: 30px;
width: 32%;
background: rgba(255, 255, 255, 0.5);
border: 1px solid #3782b4 !important;
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
}
</style>
9 条评论
这篇文章不错!
建议在揭露问题时提供建设性解决方案。
这篇文章如同一幅色彩斑斓的画卷,每一笔都充满了独特的创意。
建议提出分阶段实施路径,增强可行性。
作者的才华横溢,让这篇文章成为了一篇不可多得的艺术品。
配图与文字相辅相成,直观易懂。
哈哈哈,写的太好了https://www.lawjida.com/
《死亡视频网页》恐怖片高清在线免费观看:https://www.jgz518.com/xingkong/35197.html
博主真是太厉害了!!!