Openlayers使用WebGL加载多种自定义图标矢量点

概要

使用OpenLayers的WebGLPointsLayer加载海量矢量点(100W),并使用多种自定义图标样式
你需要满足以下环境要求:

  • 支持WebGL的浏览器(Chrome、Firefox、Safari和Edge等)。
  • 计算机具有一定的图形处理能力(显卡)。
  • 一定的计算机内存和网络带宽
  • 一点耐心

整体思路

1.创建一个包含所有图标的精灵图,每个图标在精灵图中占据一个正方形区域。精灵图可以使用简单的等分布局,也可以根据实际需求使用其他布局算法。

2.WebGLPointsLayer的Style函数无法获取返回对象feature,但可以在表达式中返回部分属性值。可以根据每个矢量点的属性值(例如图标类型),在映射文件中找到对应图标在精灵图中的纹理坐标

3.将得到的纹理坐标赋值给每个矢量点的Style函数。这样,WebGLPointsLayer就可以根据纹理坐标来渲染每个点的图标。

4.在渲染过程中,WebGLPointsLayer会根据每个点的纹理坐标从精灵图中获取对应的图标部分,并将其渲染到屏幕上。


一、核心代码:WebGLPointsLayer加载矢量点

<template>
  <div ref="mapContainer" class="mapContainer" id="mapContainer"></div>
</template>
<script lang="ts" setup>
import "ol/ol.css";
import { Feature, Map, View } from "ol";
import { Point } from "ol/geom";
import WebGLPointsLayer from "ol/layer/WebGLPoints";
import { Vector as VectorSource, } from "ol/source";
import GeoJSON from "ol/format/GeoJSON";
import { fromLonLat } from "ol/proj";
import TileLayer from "ol/layer/Tile";
import { onMounted, shallowRef } from "vue";
import { XYZ } from 'ol/source'
import { defaults as defaultControls } from "ol/control"

// 改为你的GeoJson数据地址
const pointsDataURL = './geoJson/20W'
// 改为你的精灵图地址
const imageSrc = './public/image/texture.png'
// 地图容器
const mapContainer = shallowRef<HTMLDivElement>()
//地图对象
const map = shallowRef<Map>()

/**
 * @description 创建地图实例
 * @param {Document | DocumentId} target 地图容器
 * @returns 地图对象
 */
const createMap = function (target: HTMLElement | string,): Map {
  // 创建地图
  const map = new Map({
    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: false
        })
      })
    ],
    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 >>>>>>>> 地图上添加矢量点(WebGL方式)
 * @param { Map } map 地图对象
 * @param { any } pointData GeoJson格式数据
 */
function addPointVector_WebGL(map: Map, pointData: any) {
  // 添加数据源
  let features = new GeoJSON({
    dataProjection: "EPSG:4326",
    featureProjection: "EPSG:3857",
  }).readFeatures(pointData) as Feature<Point>[];
  // 创建WebGL图层
  const webGLLayer = new WebGLPointsLayer({
    source: new VectorSource<Point>({
      features: features, // yourFeatures是包含要素的数组
    }),
    style: {
      symbol: {
        symbolType: "image",
        rotation: ["get", "rotation"],
        src: imageSrc,
        size: [32, 32],
        color: [
          "match",
          ["get", "color"],
          0, "red",
          1, "blue",
          2, "green",
          3, "pink",
          // 默认颜色
          "black",
        ],
        opacity: ["get", "opacity"],
        rotateWithView: false,
        /**
         * @description textureCoord纹理坐标使用百分比切割图
         * 每次在横纵方向移动一个单位(1/横向排列图标数量||1/纵向排列图标数量)获取下一个纹理坐标
         */
        textureCoord: [
          "match",
          ["get", "englishType"], // get返回0-1之间的数
          0.00, textureCoordData.a,
          0.01, textureCoordData.b,
          0.02, textureCoordData.c,
          0.03, textureCoordData.d,
          0.04, textureCoordData.e,
          0.05, textureCoordData.f,
          0.06, textureCoordData.g,
          0.07, textureCoordData.h,
          0.08, textureCoordData.i,
          0.09, textureCoordData.j,
          0.10, textureCoordData.k,
          0.11, textureCoordData.l,
          0.12, textureCoordData.m,
          0.13, textureCoordData.n,
          0.14, textureCoordData.o,
          0.15, textureCoordData.p,
          0.16, textureCoordData.q,
          0.17, textureCoordData.r,
          0.18, textureCoordData.s,
          0.19, textureCoordData.t,
          0.20, textureCoordData.u,
          0.21, textureCoordData.v,
          0.22, textureCoordData.w,
          0.23, textureCoordData.x,
          0.24, textureCoordData.y,
          //默认
          textureCoordData.z,
        ],
      },
    },
    disableHitDetection: true, // 禁用命中检测以提高性能
  });
  map.addLayer(webGLLayer);
}
// 由于篇幅没有放出完整的textureCoord,后续有数据下载链接和表达式生成代码
const textureCoordData = {
  "x": [
    "match",
    ["get", "name"],
    "4600",
    [0, 0.92, 0.02, 0.94],
    "4700",
    [0, 0.94, 0.02, 0.96],
    [0.98, 0.98, 1, 1]
  ],
  "y": [
    "match",
    ["get", "name"],
    // "4801",// 假设4801和4802没有纹理,位置的坐标和编码应注释掉
    // [0.02, 0.96, 0.04, 0.98],
    // "4802",
    // [0.04, 0.96, 0.06, 0.98],
    "4800",
    [0, 0.96, 0.02, 0.98],
    "4900",
    [0, 0.98, 0.02, 1],
    [0.98, 0.98, 1, 1]
  ],
  "z": [0.98, 0.98, 1, 1]
}
onMounted(() => {
  // 创建地图
  map.value = createMap(mapContainer.value)
  // 打开GeoJson数据
  fetch(`${pointsDataURL}.json`)
    .then(response => response.json())
    .then(pointsData => {
      // 加载矢量点
      addPointVector_WebGL(map, pointsData)
    })
})
</script>
<style>
#mapContainer {
  position: absolute;
  top: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
}
</style>

效果:
加载100W矢量点效果

二、精灵图制作

TexturePackerGUI是一个非常实用的图形用户界面(GUI)工具,它能够将多张小图像合并成一张大图像,从而创建纹理图集。本文为便于编码和计算纹理坐标,使用TexturePackerGUI将精灵图强制处理成正方形。
制作规范:

  • 每个子图标应为大小一致的正方形(本文选用48×48)。
  • 图标共2500个,以0-49编号,放置于0-49编号的文件夹中。精灵图成图即为横向、纵向均为50个单位的图标阵列。
  • 编号0304即对应文件夹3中名称为4的图片,将你的图标替换对应编号文件夹中对应编号的图标。
  • 使用时将所有文件夹拖入TexturePackerGUI,按下方设置生成精灵图成图。

制作资源结构一览:
请添加图片描述
每个文件夹内:
请添加图片描述
TexturePackerGUI设置:
请添加图片描述
请添加图片描述

制作完成的精灵图长这样↓↓↓只在第一列放置了图标。
精灵图

三、生成矢量点数据

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

  • name:名称/编码,类型为 string | number,对应文件夹+图片名称编码
  • rotationx:旋转角度,范围0~1
  • englishType:分类,范围0~1(不接受字符串,啊,难受)
  • opacity:透明度,范围0~1
  • color:颜色匹配值,类型为 number(依然不接受字符串、rgb色值,所以以match匹配值的方式实现颜色区分)

数据样例:

{
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "properties": {
                "rotation": 0.01,
                "name": "3500",
                "englishType": 0.17,
                "opacity": 0.4,
                "color": 2
            },
            "geometry": {
                "coordinates": [
                    -15.183829600909434,
                    63.574883642733454
                ],
                "type": "Point"
            }
        }
}

四、生成match表达式

测试出来的特性:
1.textureCoord使用的match表达式,数组的长度有限制,大约可以支持500个元素。如果只使用一层match,那么最多只能匹配大约250个不同的图标。

2.match表达式允许嵌套,一层嵌套可以看作是要素的一级分类,比如汽车、飞机、船,而二层嵌套则是详细分类,比如公交车、轿车、快递车(嵌套层级无限制)。理论上嵌套后图标数目不受限,因此在textureCoordData中使用了多层嵌套的结构。
嵌套match表达式解析:

// 为了便于理解,把二级match表达式的匹配项代入一级表达式
          [
          "match",// 指示使用match表达式规则进行匹配
          ["get", "englishType"], // 获取属性字段名称为"englishType"的属性值
          0.23,  // 一次匹配,匹配值为0.23时
          // 0.23的匹配结果是一个match表达式
          ["match",
          ["get", "name"],// 获取属性字段名称为"name"的属性值
           "4600",// 二次匹配,匹配值为"4600"时
           [0, 0.92, 0.02, 0.94],// 匹配成功,将应用的纹理坐标
           "4700",// 二次匹配,匹配值为"4700"时
           [0, 0.94, 0.02, 0.96],// 匹配成功,将应用的纹理坐标
            [0.98, 0.98, 1, 1]// 匹配失败,应用的纹理坐标(默认)
          ],
          0.24,
          // 0.24的匹配结果是一个match表达式
          ["match", 
          ["get", "name"],// 二次匹配
          "4800",// 匹配值
          [0, 0.96, 0.02, 0.98],// 匹配结果
          "4900",// 匹配值
          [0, 0.98, 0.02, 1],// 匹配结果
          [0.98, 0.98, 1, 1]// 匹配失败,默认值
          ]
          [0.98, 0.98, 1, 1]// 匹配失败,默认值
        ],

3.过于复杂的match表达式可能会导致性能问题,因此不宜将嵌套层数/表达式选项设置过多。

4.textureCoordData中,应注释掉没有实际使用的图标纹理坐标和匹配值,以节省性能。
如下图,假设编号4801和4802没有纹理,位置的坐标和编码应注释掉↓↓↓:

const textureCoordData = {
  "y": [
    "match",
    ["get", "name"],
    // 假设4801没有纹理,位置的坐标和编码应注释掉
    // "4801",
    // [0.02, 0.96, 0.04, 0.98],
    // 假设4802没有纹理,位置的坐标和编码应注释掉
    // "4802",
    // [0.04, 0.96, 0.06, 0.98],
    "4800",
    [0, 0.96, 0.02, 0.98],
    "4900",
    [0, 0.98, 0.02, 1],
    [0.98, 0.98, 1, 1]
  ],
  "z": [0.98, 0.98, 1, 1]
}

5.[“get”, “name”]可以返回number或string类型的值,但是对于[“get”, “其他键名”],它只能返回0到1之间的小数。因此,[“get”, “englishType”]的匹配值是诸如0.01这样的小数。

6.OpenLayers 7中的textureCoord表达式与OpenLayers 6稍有不同(纹理坐标),具体的用法和语法可能会有所区别。如果使用OpenLayers 6,请参考官方文档和示例以获取更新的表达式规则。

  • 44
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

無可言喻

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

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

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

打赏作者

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

抵扣说明:

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

余额充值