使用技术: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>
最后修改:2024 年 08 月 20 日
如果觉得我的文章对你有用,请随意赞赏