一、概要
在使用 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"
}
},
]
}
- 矢量点数据(GeoJson)生成工具:https://blog.csdn.net/qq_40236953/article/details/134954087