Skip to content

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,
  }
}

Released under the ISC License.