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()
相关推荐
谢慧琼9 小时前
2026零基础做企业网站,低成本搭建官方网站
服务器·前端·javascript
拾年27511 小时前
React Hooks 进阶篇:useMemo / useCallback / useReducer / useImperativeHandle,用「算账 +
前端·javascript·react.js
Jackson__12 小时前
面试官:如何在老项目中使用新框架,并实现通信?
前端·javascript·面试
拾年27512 小时前
React Hooks 保姆级教程:把组件想象成失忆的打工人,4 个 Hook 带你原地起飞
前端·javascript·react.js
默_笙12 小时前
😝 5 亿次循环卡死页面?我用 Web Worker 把它扔进后台线程,页面丝滑如初
前端·javascript
CAD老兵12 小时前
一行代码集成 DWG/DXF 图纸查看:测量批注,数据不出站
前端·javascript·github
小林ixn12 小时前
单例模式:从弹窗管理到全局状态,一个模式搞定
前端·javascript·设计模式
猫不易12 小时前
从 Virtual DOM 到 Vapor:Vue 3.6 的另一条路
前端·vue.js
名字还没想好☜12 小时前
React 用 useEffect 做轮询实战:setInterval 拿到旧 state 的闭包陷阱与正确清理
前端·javascript·react.js·react·useeffect