vue openlayers地图加载大量线条时优化

vue openlayers地图加载大量线条时优化

如果一次性加载上万个带标题的线条,会造成地图卡顿, 优化方法是只加载当前视口内的点位,且只显示屏幕中心的线条的标题, 每次拖动地图只加载视口内的线条

  • 后端返回的数据,扁平化后如下:
js 复制代码
[
    {label: '', color: '', featureFile: 'geojson数据', level: 1, children: []}, 
    {label: '', color: '', featureFile: 'geojson数据', level: 2, children: []},
    {label: '', color: '', featureFile: 'geojson数据', level: 3, children: []},
    {label: '', color: '', featureFile: 'geojson数据', level: 4, children: []},
    {label: '', color: '', featureFile: 'geojson数据', level: 5, children: []},
    {label: '', color: '', featureFile: 'geojson数据', level: 1, children: []}, 
    {label: '', color: '', featureFile: 'geojson数据', level: 2, children: []},
    {label: '', color: '', featureFile: 'geojson数据', level: 3, children: []},
    {label: '', color: '', featureFile: 'geojson数据', level: 4, children: []},
    {label: '', color: '', featureFile: 'geojson数据', level: 5, children: []},
]

当地图缩放改变时会根据level加载指定level级别的数据

  • 工具类OlViewportLineUtil.js
js 复制代码
import { Vector as VectorLayer } from 'ol/layer'
import { Vector as VectorSource } from 'ol/source'
import GeoJSON from 'ol/format/GeoJSON'
import { Style, Stroke, Text, Fill } from 'ol/style'
import { intersects, getCenter } from 'ol/extent'
import Point from 'ol/geom/Point'
import { distance } from 'ol/coordinate'

export default class OlViewportLineUtil {
  constructor(map) {
    this.map = map
    this.geoFormat = new GeoJSON()

    // 线条图层
    this.lineVectorSource = new VectorSource({ useSpatialIndex: true })
    this.lineVectorLayer = new VectorLayer({
      source: this.lineVectorSource,
      name: '线条图层',
      zIndex: 999,
      declutter: true,
      declutterMode: 'label',
      style: (feature, resolution) => {
        const showLabel = getIsShowFeatureText(this.map, feature)

        let featureText = feature.get('NAME')
        const zoom = Math.round(this.map.getView().getZoom())

        return new Style({
          stroke:
            new Stroke({
                  color: '#00A0E9',
                  width: 3,
                }),
          fill: new Fill({
            color: '#00A0E9',
          }),
          text: showLabel
            ? new Text({
                offsetY: -10,
                font: '12px 微软雅黑',
                placement: 'line',
                text: featureText,
                fill: new Fill({
                  color: '#fff',
                }),
                stroke: new Stroke({
                  color: '#006614',
                  width: 3,
                }),
                overflow: true,
              })
            : null,
        })
      },
    })
    this.map.addLayer(this.lineVectorLayer)

    this.lineMetas = []
    this.loadedInView = new Set()
  }

  setRiverTree(tree) {
    this.lineMetas = this.flattenTree(tree)
    this.refreshView()
  }

  refreshView() {
    const view = this.map.getView()
    const zoom = Math.round(view.getZoom())
    const viewExtent = view.calculateExtent()

    const w = viewExtent[2] - viewExtent[0]
    const h = viewExtent[3] - viewExtent[1]
    const bufferExtent = [viewExtent[0] - w * 0.3, viewExtent[1] - h * 0.3, viewExtent[2] + w * 0.3, viewExtent[3] + h * 0.3]

    const shouldShow = new Set()
    this.lineMetas.forEach((meta) => {
      const level = Number(meta.level)
      if (zoom < 6.5 + level) return
      if (meta.extent) {
        if (!intersects(bufferExtent, meta.extent)) return
      }

      shouldShow.add(meta.label)
    })

    this.loadedInView.forEach((label) => {
      if (!shouldShow.has(label)) {
        const f = this.lineVectorSource.getFeatureById(label)
        f && this.lineVectorSource.removeFeature(f)
        this.loadedInView.delete(label)
      }
    })

    shouldShow.forEach(async (label) => {
      if (this.loadedInView.has(label)) return
      const meta = this.lineMetas.find((m) => m.label === label)
      if (!meta || !meta.featureFile) return

      try {
        const geojson = JSON.parse(meta.featureFile)
        let feature
        if (geojson.type === 'FeatureCollection') {
          const features = this.geoFormat.readFeatures(geojson)
          feature = features[0]
        } else {
          feature = this.geoFormat.readFeature(geojson)
        }

        if (feature) {
          feature.setId(label)
          feature.set('label', meta.label)
          feature.set('color', meta.color)
          feature.set('level', meta.level)
          this.lineVectorSource.addFeature(feature)
          this.loadedInView.add(label)
        }
      } catch (e) {
        
      }
    })

    this.lineVectorLayer.changed()
  }

  flattenTree(tree) {
    const list = []
    const loop = (nodes) => {
      nodes.forEach((item) => {
        if (item.featureFile) {
          try {
            const geojson = JSON.parse(item.featureFile)
            let feat = null
            if (geojson.type === 'FeatureCollection') {
              const features = this.geoFormat.readFeatures(geojson)
              feat = features[0]
            } else {
              feat = this.geoFormat.readFeature(geojson)
            }
            item.extent = feat?.getGeometry()?.getExtent() || null
          } catch (e) {
            item.extent = null
          }
          list.push(item)
        }
        if (item.children?.length) loop(item.children)
      })
    }
    loop(tree)
    return list
  }

  watchMap() {
    this.map.on('movestart', () => {
      this.lineVectorLayer.changed()
    })

    this.map.on('moveend', () => {
      this.refreshView()
    })
  }

  clear() {
    this.lineMetas = []
    this.loadedInView = new Set()
    this.lineVectorSource.clear()
    this.loadedInView.clear()
    this.lineVectorLayer.changed()
  }

  destroy() {
    this.clear()
    this.map.removeLayer(this.lineVectorLayer)
  }


}



/**
 * 只显示屏幕中心附近的名称, 名称多了很卡顿
 * @param {*} map
 * @param {*} feature
 * @returns 是否显示
 */
function getIsShowFeatureText(map, feature) {
  const view = map.getView()
  const mapSize = map.getSize()
  if (!mapSize) return new Style({ stroke: new Stroke({ color: '#00A0E9', width: 2 }) })

  const viewExtent = view.calculateExtent(mapSize)
  const [minx, miny, maxx, maxy] = viewExtent
  const w = maxx - minx
  const h = maxy - miny

  const centerBuffer = 0.3
  const centerExtent = [minx + w * centerBuffer, miny + h * centerBuffer, maxx - w * centerBuffer, maxy - h * centerBuffer]

  const geom = feature.getGeometry()
  const showLabel = geom && intersects(geom.getExtent(), centerExtent)
  return showLabel
}
  • 使用
js 复制代码
import OlViewportLineUtil from '@/util/OlViewportLineUtil.js'

this.lineUtil = new OlViewportLineUtil(this.map)

const list = [
    {label: '', color: '', featureFile: 'geojson数据', level: 1, children: []}, 
    {label: '', color: '', featureFile: 'geojson数据', level: 2, children: []},
    {label: '', color: '', featureFile: 'geojson数据', level: 3, children: []},
    {label: '', color: '', featureFile: 'geojson数据', level: 4, children: []},
    {label: '', color: '', featureFile: 'geojson数据', level: 5, children: []},
    {label: '', color: '', featureFile: 'geojson数据', level: 1, children: []}, 
    {label: '', color: '', featureFile: 'geojson数据', level: 2, children: []},
    {label: '', color: '', featureFile: 'geojson数据', level: 3, children: []},
    {label: '', color: '', featureFile: 'geojson数据', level: 4, children: []},
    {label: '', color: '', featureFile: 'geojson数据', level: 5, children: []},
]
this.lineUtil.setRiverTree(list)
this.lineUtil.watchMap()
相关推荐
qq_2518364576 小时前
SpringBoot+Vue 共享电池柜管理系统 完整实现 前后端分离项目实战 完整代码
vue.js·spring boot·后端
之歆7 小时前
Day01_ES6+ 专业指南:从基础到实战的现代JavaScript开发(下)
前端·javascript·es6
kyriewen8 小时前
AI生成代码快如闪电,但我修了三个小时——它到底帮了谁?
前端·javascript·ai编程
竹林8188 小时前
用 wagmi v2 和 viem 手写 NFT 市场批量上架功能,我踩遍了所有异步坑
javascript
zithern_juejin9 小时前
数组扁平化
javascript
清溪5499 小时前
n8n表达式沙箱逃逸至RCE漏洞-CVE-2025-68613复现
javascript·安全
Hilaku9 小时前
多标签页并发请求导致 Token 刷新失败?只有 15行代码就能解决 !
前端·javascript·程序员
烛衔溟9 小时前
TypeScript 类的静态成员与静态方法
开发语言·javascript·typescript
Nile9 小时前
解密Palantir系列一:4. Ontology 不是哲学
开发语言·前端·javascript
Highcharts9 小时前
如何创建蛛网地图|气泡事件+全球发布+关联组合图表开发示例
javascript