OpenLayers 图标加载性能优化:使用 Style 样式缓存

一、概要

在使用 OpenLayers 为大量矢量点应用图标样式进行地图渲染时,频繁地创建 Style 对象会增加内存消耗影响渲染效率和导致图标闪烁

例如以下方法,根据不同的颜色和透明度,渲染矢量点图标:

/**
 * 获取给定要素的样式
 * @param { Feature<Geometry> } feature 矢量要素
 * @returns {Style} 要素样式数组
 */
function getStyle(feature: Feature<Geometry>): Style {
  const baseColor = feature.get('color') ?? 'rgba(0, 0, 0, 1)'; // 默认颜色为黑色
  const opacity = feature.get('opacity') ?? 1; // 默认透明度为1
  const rotation = feature.get('rotation') ?? 0; // 默认旋转为0
  // 将透明度添加到颜色字符串中
  const color = `${baseColor.slice(0, -1)}, ${opacity})`;

  // 为每个要素定义样式
  return new Style({
    image: new Icon({
      scale: 1,
      src: "/img/ship.png",
      color,
      rotation,
      crossOrigin: 'anonymous',
    })
  })
}

测试,加载10000个数据,出现图标闪烁问题。

请添加图片描述
为优化渲染图标的性能,可以创建样式池复用 Style 对象,而不是每次都新建。以显著减少内存消耗并提高性能。


二、完整代码:渲染矢量点,使用样式缓存策略

<template>
  <!--地图-->
  <div ref="mapContainer" class="mapContainer" id="mapContainer"></div>
</template>

<script lang="ts" setup>
import { onMounted, shallowRef } from 'vue'
import { View, Map as OLMap, Feature } from "ol"
import { fromLonLat } from 'ol/proj'
import TileLayer from 'ol/layer/Tile'
import { XYZ } from 'ol/source'
import { defaults as defaultControls } from "ol/control"
import { Vector as VectorLayer } from 'ol/layer'
import VectorSource from 'ol/source/Vector'
import { Circle as CircleStyle, Style, Icon } from "ol/style";
import { FeatureLike } from 'ol/Feature'
import { Geometry } from 'ol/geom'
import GeoJSON from 'ol/format/GeoJSON'

// 改为你自己的GeoJson数据地址
const pointDataURL = './geoJson/1W'
// 地图容器
const mapContainer = shallowRef<HTMLDivElement>()
// 地图对象
const map = shallowRef<OLMap>()

/**
 * @description 创建地图实例
 * @param {Document | DocumentId} target 地图容器
 * @returns 地图对象
 */

const createMap = function (target: HTMLElement | string,): OLMap {
  // 创建地图
  const map = new OLMap({
    target,
    layers: [
      new TileLayer({
        source: new XYZ({
          url: "http://t0.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=99a8ea4a53c8553f6f3c565f7ffc15ec",
          crossOrigin: 'anonymous',
          wrapX: true
        })
      })
    ],
    controls: defaultControls({
      zoom: false, // 不显示放大放小按钮
      rotate: false, // 不显示指北针控件
      attribution: false, // 不显示右下角的地图信息控件
    }).extend([]),
    view: new View({
      projection: 'EPSG:3857', // 坐标系EPSG:4326或EPSG:3857
      zoom: 6, // 打开页面时默认地图缩放级别
      maxZoom: 20,
      minZoom: 1, // 地图缩放最小级别
      center: fromLonLat([121.5, 25]), // 需要转到墨卡托坐标系
      constrainResolution: true, // 自动缩放到距离最近的一个整数级别,因为当缩放在非整数级别时地图会糊
    })
  })
  return map
}

/**
 * @description 新建图层(检测到同名图层直接获取)
 * @param map 地图实例
 * @param layerName 图层名
 * @param getStyle feature样式
 * @returns 
 */

function getVectorLayer(map: OLMap, layerName: String, getStyle: Function): VectorLayer<VectorSource> {
  let vectorLayer = map.getLayers().getArray().find(layer => layer.get('name') === layerName) as VectorLayer<VectorSource>;
  if (!vectorLayer) {
    vectorLayer = new VectorLayer({
      source: new VectorSource({ wrapX: true, features: [] }),
      style: function (feature: FeatureLike) {
        return getStyle(feature)
      }
    });
    vectorLayer.set('name', layerName);
    map.addLayer(vectorLayer);
  }
  return vectorLayer;
}

/**
 * @description >>>>>>>> 添加矢量点(可追加数据)
 * @param { Map } map 地图对象
 * @param { string} layerName 图层名
 * @param { any } pointData 点据集合
 */

function addPointVector(
  map: OLMap,
  layerName: string,
  pointData: any) {
  if (!map || !pointData) { return }
  // 获取矢量图层
  let vectorLayer: VectorLayer<VectorSource> | null = getVectorLayer(map, layerName, getStyle)
  // 添加数据源
  let features = new GeoJSON({ dataProjection: 'EPSG:4326', featureProjection: 'EPSG:3857' }).readFeatures(pointData)
  vectorLayer?.getSource()!.addFeatures(features)
}
// 全局样式缓存
const styleCache: Record<string, Style> = {};

/**
 * 获取给定要素的样式
 * @param { Feature<Geometry> } feature 矢量要素
 * @returns { Array<Style> } 要素样式数组
 */
function getStyle(feature: Feature<Geometry>): Style {
  // 从要素中获取样式属性,设置默认值
  const baseColor = feature.get('color') ?? 'rgba(0, 0, 0, 1)'; // 默认颜色为黑色
  const opacity = feature.get('opacity') ?? 1; // 默认透明度为1
  const rotation = feature.get('rotation') ?? 0; // 默认旋转为0
  // 将透明度添加到颜色字符串中
  const color = `${baseColor.slice(0, -1)}, ${opacity})`;

  // 为单个要素定义样式
  const style = new Style({
    image: new Icon({
      scale: 1,
      src: "/img/ship.png",// 图片需要自己找,放在public/img内
      color,
      rotation,
      crossOrigin: 'anonymous',
    })
  })
  // 使用唯一的缓存键存储样式
  const cacheKey = `${color}-${rotation}`;
  if (!styleCache[cacheKey]) {
    styleCache[cacheKey] = style;
  }
  return styleCache[cacheKey];
}


onMounted(async () => {
  map.value = createMap(mapContainer.value!);
  // 打开GeoJson
  const response = await fetch(`${pointDataURL}.json`);
  const point = await response.json();
  // 加载Point
  addPointVector(map.value, 'pointLayer', point)
});


</script>
<style lang="scss">
#mapContainer {
  position: absolute;
  top: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
}
</style>

请添加图片描述

三、核心代码:使用样式池复用样式

// 全局样式缓存
const styleCache: Record<string, Style> = {};

/**
 * 获取给定要素的样式
 * @param { Feature<Geometry> } feature 矢量要素
 * @returns { Array<Style> } 要素样式数组
 */
function getStyle(feature: Feature<Geometry>): Style {
  // 从要素中获取样式属性,设置默认值
  const baseColor = feature.get('color') ?? 'rgba(0, 0, 0, 1)'; // 默认颜色为黑色
  const opacity = feature.get('opacity') ?? 1; // 默认透明度为1
  const rotation = feature.get('rotation') ?? 0; // 默认旋转为0
  // 将透明度添加到颜色字符串中
  const color = `${baseColor.slice(0, -1)}, ${opacity})`;

  // 为单个要素定义样式
  const style = new Style({
    image: new Icon({
      scale: 1,
      src: "/img/ship.png", // 图片需要自己找,放在public/img内
      color,
      rotation,
      crossOrigin: 'anonymous',
    })
  })

  // 使用唯一的缓存键存储样式
  const cacheKey = `${color}-${rotation}`;
  if (!styleCache[cacheKey]) {
    styleCache[cacheKey] = style;
  }
  return styleCache[cacheKey];
}
  • 主要思路
    1.样式函数外部初始化样式池(styleCache)对象
    2.样式函数内部获取样式属性并设置默认值
    3.定义样式,创建唯一缓存键 (cacheKey),根据唯一键存储在样式池中。

  • 注意事项
    1.键的唯一性:当前的缓存机制只使用颜色和旋转角度作为缓存键。如果样式属性组合更多,可增加更多的属性到 cacheKey 中,以确保唯一性。
    2.尽量避免在 getStyle 中进行复杂计算,确保 getStyle 函数中只包含必要的逻辑,以减少性能开销。

四、数据说明

本文使用的矢量点数据为标准的GeoJson数据,制作工具下方自取,属性字段包括:

  • name:名称/编码,类型为 string | number
  • rotation:旋转角度,范围0~1
  • opacity:透明度,范围0~1
  • color:rgba颜色

数据样例:

 {
    "type": "FeatureCollection",
    "features": [
      {
            "type": "Feature",
            "properties": {
                "rotation": 0.13,
                "name": "1500",
                "englishType": 0.07,
                "opacity": 0.5,
                "color": "rgba(57, 174, 24)"
            },
            "geometry": {
                "coordinates": [
                    -118.70523953338989,
                    -1.3911765556242415
                ],
                "type": "Point"
            }
        },
    ]
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

無可言喻

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值