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()
相关推荐
竹林8182 小时前
Next.js 14 + wagmi v2 构建 NFT 市场:从列表渲染到链上交易的全链路实践
javascript·next.js
幸运小圣2 小时前
Array.prototype.reduce 全面解析【JS方法】
开发语言·javascript·原型模式
Ruihong2 小时前
你的 Vue TransitionGroup 组件,VuReact 会编译成什么样的 React 代码?
vue.js·react.js·面试
星晨雪海2 小时前
若依框架原有页面功能进行了点位管理模块完整改造(3)
开发语言·前端·javascript
sensen_kiss2 小时前
CAN302 Coursework1对 JavaScript 和 PHP 的考察
javascript·学习·php
morethanilove2 小时前
新建vue3 + ts +vite 项目
前端·javascript·vue.js
忆往wu前2 小时前
搞懂 SPA 再学路由!Vue Router 从0到完善 + 嵌套路由一次性梳理
前端·vue.js
小智社群2 小时前
获取贝壳中介列表,并且自动导入excel
开发语言·javascript·ecmascript