useDrawPolygon
简介
在地图上绘制区域
使用示例
点我查看代码
vue
<script lang="ts" setup>
import type { OlMapInst } from '@summeruse/ol'
import type { Coordinate } from 'ol/coordinate'
import {
createStyle,
createVectorLayer,
getOSMLayer,
OlMap,
useDrawPolygon,
wgs84ToMercator,
} from '@summeruse/ol'
import { NButton, NCard, NConfigProvider, NDataTable, useMessage } from 'naive-ui'
import { Feature, Map as OLMap } from 'ol'
import { Polygon } from 'ol/geom'
import { h, onMounted, ref, watch } from 'vue'
const olMapInst = ref<OlMapInst>()
const olMap = new OLMap()
olMap.addLayer(getOSMLayer())
const message = useMessage()
const center = wgs84ToMercator([120, 30])
interface Area {
id: number
name: string
coordinates: Coordinate[][]
}
const areaList = ref<Area[]>([])
const isDraw = ref(false)
function getData() {
// 模拟数据
return new Promise<Area[]>((resolve) => {
setTimeout(() => {
resolve([
{
id: 1,
name: '区域1',
coordinates: [
[
[13349319.325855177, 3521923.394178423],
[13375307.91547214, 3510610.713992217],
[13363077.99094651, 3487985.353619805],
[13338618.141895253, 3501591.1446545664],
[13349319.325855177, 3521923.394178423],
],
],
},
{
id: 2,
name: '区域2',
coordinates: [
[
[13382340.122074375, 3532777.4521949184],
[13378824.018773258, 3530637.2154029333],
[13374084.923019577, 3529108.47483723],
[13371333.19000131, 3527579.7342715263],
[13372250.434340732, 3523452.134744127],
[13376989.530094413, 3519171.661160157],
[13378365.396603547, 3516725.6762550315],
[13393347.05414744, 3525592.371536112],
[13382340.122074375, 3532777.4521949184],
[13382340.122074375, 3532777.4521949184],
],
],
},
])
}, 100)
})
}
const columns = [
{
title: '名称',
key: 'name',
width: 100,
},
{
title: '操作',
key: 'action',
render(row: any) {
return h(
'div',
{
class: 'flex justify-center items-center gap-2',
},
[
h(
NButton,
{
type: 'info',
disabled: isDraw.value,
onClick: () => {
editLine(row)
},
},
{ default: () => '编辑' },
),
h(
NButton,
{
type: 'error',
disabled: isDraw.value,
onClick: () => {
areaList.value = areaList.value.filter(item => item.id !== row.id)
},
},
{ default: () => '删除' },
),
],
)
},
},
]
const { source, layer: vectorLayer } = createVectorLayer({
style: createStyle({
strokeOptions: {
color: 'rgba(0, 0, 255, 1.0)',
width: 2,
},
}),
})
olMap.addLayer(vectorLayer)
watch(
() => areaList.value,
(newVal) => {
source.clear()
newVal.forEach((item) => {
const feature = new Feature({
geometry: new Polygon(item.coordinates),
})
source.addFeature(feature)
})
},
{
deep: true,
immediate: true,
},
)
const drawId = ref<number>()
const { start, stop, coordinates, setFeatures, features } = useDrawPolygon(olMap, {
size: 1,
})
function editLine(area?: Area) {
vectorLayer.setOpacity(0.2)
isDraw.value = true
drawId.value = area?.id || undefined
setFeatures(area ? [area.coordinates] : undefined)
start()
}
function addLine() {
editLine()
}
function saveLine() {
if (features.value.length === 0)
return message.error('请先绘制多边形')
if (drawId.value) {
const index = areaList.value.findIndex(item => item.id === drawId.value)
areaList.value[index].coordinates = coordinates.value[0]
}
else {
areaList.value.push({
id: areaList.value.length + 1,
name: `航线${areaList.value.length + 1}`,
coordinates: coordinates.value[0],
})
}
setFeatures()
stop()
isDraw.value = false
drawId.value = undefined
vectorLayer.setOpacity(1)
}
onMounted(async () => {
const res = await getData()
areaList.value = res
})
</script>
<template>
<NConfigProvider>
<NCard title="区域列表" class="mb-2">
<NDataTable :columns :data="areaList" />
<NButton @click="addLine">
添加区域
</NButton>
<NButton :disabled="!isDraw" @click="saveLine">
保存
</NButton>
</NCard>
<OlMap ref="olMapInst" class="w-100% h-400px" :zoom="10" :center :ol-map />
{{ coordinates }}
</NConfigProvider>
</template>
源代码
点我查看代码
ts
import type { Coordinate } from 'ol/coordinate'
import type { Style } from 'ol/style'
import type { VNode } from 'vue'
import type { OLMap } from '../../types'
import type { StyleOptions } from '../../utils'
import { Feature, Overlay } from 'ol'
import { Polygon } from 'ol/geom'
import { Draw, Modify } from 'ol/interaction'
import VectorLayer from 'ol/layer/Vector'
import VectorSource from 'ol/source/Vector'
import { computed, onMounted, onUnmounted, ref, render } from 'vue'
import { createStyle } from '../../utils'
import { getExtentCenter } from '../../utils/calculate'
import { mercatorExtentToWgs84, wgs84ToMercator } from '../../utils/projection'
import { createToolTipElement } from '../common'
export interface DrawPolygonOptions {
defaultCoordinates?: Coordinate[][][] // 默认多边形坐标坐标
deletePointLabel?: VNode // 删除点的标签
deleteFeatureLabel?: VNode // 删除多边形的标签
style?: Style // 线条样式
styleOptions?: StyleOptions // 线条样式配置
drawStyleOptions?: StyleOptions // 绘制时的样式配置
modifyStyleOptions?: StyleOptions // 修改时的样式配置
drawStyle?: Style // 绘制时的样式
modifyStyle?: Style // 修改时的样式
zIndex?: number // 图层z-index
size?: number // 最大线条数量
}
export function useDrawPolygon(olMap: OLMap, options: DrawPolygonOptions) {
const inDraw = ref(true)
const features = ref<Feature<Polygon>[]>([])
const coordinates = computed(() => {
return features.value.map((feature) => {
return feature.getGeometry()!.getCoordinates()
})
})
const style = options.styleOptions ? createStyle(options.styleOptions) : options?.style
const source = new VectorSource() // 创建一个矢量图层
const layer = new VectorLayer({
source,
style,
zIndex: options?.zIndex,
})
olMap.addLayer(layer)
const drawStyle = options.drawStyleOptions ? createStyle(options.drawStyleOptions) : options?.drawStyle
const draw = new Draw({
source,
style: drawStyle || style,
type: 'Polygon',
})
draw.setActive(false) // 默认不激活
const modifyStyle = options.modifyStyleOptions ? createStyle(options.modifyStyleOptions) : options?.modifyStyle
const modify = new Modify({
source,
style: modifyStyle || style,
})
const overlaySet = new Set<Overlay>()
olMap.addInteraction(draw)
onMounted(() => {
olMap.addInteraction(modify)
})
source.on('addfeature', () => {
if (options.size) {
if (source.getFeatures().length >= options.size) {
draw.setActive(false)
}
}
})
source.on('removefeature', () => {
if (options.size) {
if (source.getFeatures().length < options.size) {
draw.setActive(true)
}
}
})
const clearOverlay = () => {
overlaySet.forEach((overlay) => {
olMap.removeOverlay(overlay)
})
overlaySet.clear()
}
source.on('change', () => {
clearOverlay()
source.getFeatures().forEach((feature) => {
const geometry = feature.getGeometry() as Polygon
const center = getExtentCenter(mercatorExtentToWgs84(geometry.getExtent()))
let element = document.createElement('div')
if (options.deleteFeatureLabel) {
render(options.deleteFeatureLabel, element)
}
else {
element = createToolTipElement('删除区域')
}
element.addEventListener('click', () => {
source.removeFeature(feature)
})
const overlay = new Overlay({
position: wgs84ToMercator(center),
positioning: 'center-center',
element,
})
overlaySet.add(overlay)
olMap.addOverlay(overlay)
const coordinates = geometry.getCoordinates()[0]
if (coordinates.length < 3)
return
coordinates.forEach((coordinate, index) => {
let element = document.createElement('div')
if (options.deletePointLabel) {
render(options.deletePointLabel, element)
}
else {
element = createToolTipElement('删除点')
}
element.addEventListener('click', () => {
feature.setGeometry(new Polygon([coordinates.filter((_, i) => i !== index)]))
})
const overlay = new Overlay({
position: coordinate,
positioning: 'top-center',
offset: [0, -30],
element,
})
overlaySet.add(overlay)
olMap.addOverlay(overlay)
})
})
})
draw.on('change:active', () => {
inDraw.value = draw.getActive()
})
source.on('change', () => {
features.value = source.getFeatures() as Feature<Polygon>[]
})
/** 销毁 */
const destroy = () => {
clearOverlay()
olMap.removeLayer(layer)
olMap.removeInteraction(draw)
olMap.removeInteraction(modify)
}
const start = () => {
if (options.size && features.value.length >= options.size)
return
draw.setActive(true)
}
const stop = () => {
draw.setActive(false)
}
/** 重新设置坐标 */
const setFeatures = (coordinates?: Coordinate[][][]) => {
source.clear()
coordinates?.forEach((coordinates) => {
coordinates.forEach((coordinate) => {
if (coordinate.length < 2)
return
if (coordinate[0] !== coordinate[coordinate.length - 1]) {
coordinate.push(coordinate[0])
}
const feature = new Feature({
geometry: new Polygon([coordinate]),
})
source.addFeature(feature)
})
})
}
/** 恢复到默认 */
const reset = () => {
setFeatures(options.defaultCoordinates)
}
reset() // 初始化
/** 停止绘制并清空 */
const clear = () => {
source.clear()
stop()
}
onUnmounted(() => {
destroy()
})
return {
inDraw,
start,
stop,
clear,
setFeatures,
reset,
features,
coordinates,
destroy,
}
}