OpenLayers:海量图形渲染之矢量切片

最近由于在工作中涉及到了海量图形渲染的问题,因此我开始研究相关的解决方案。在咨询了许多朋友之后发现矢量切片似乎是行业内最常用的一种解决方案,于是我便开始研究它该如何使用。

一、什么是矢量切片

矢量切片按照我的理解就是用栅格切片的方式把矢量数据也切成金字塔,只不过切割的不是栅格图片,而是矢量数据的描述性文件。

矢量切片的特点

因此矢量切片它就兼具矢量数据与栅格数据的优点,比如说:

  • 矢量瓦片相比于栅格图片更加灵活,可以直接访问矢量要素,因为矢量数据是以要素为单位的,而栅格数据它就是一个图片那就难以直接访问到具体的要素了。
  • 可直接在客户端获取请求指定地物的信息,无须再次请求服务器。因为空间数据和属性数据一起被请求到客户端了,无需再次请求了。
  • 样式可改变和定制。因为我们拿到的只是矢量数据,因此就可以在客户端自由的给它设置样式。
  • 相比于原始矢量数据,矢量瓦片更小巧,进行了重新编码并切分,在被请求时可以只返回请求区域和相应级别的数据。这个优点也就是我想使用它的理由,我期望矢量切片可以帮助我解决海量图形渲染的问题。
  • 数据更新快,甚至可以说是实时的,当数据库中的空间数据变化后,再次请求的数据是更新后的数据。

二、发布矢量切片服务

如何发布矢量瓦片服务,具体要看你使用的是什么WebGIS服务器,不同的WebGIS服务器操作的步骤可能都不一样,需要专门去查阅相关资料。我在实验的过程中是使用GeoServer来发布矢量切片的。

GeoServer中发布矢量切片

1.下载Vector Tiles 插件

GeoServer中无法直接实现矢量切片,需要下载对应的插件。

插件在官网中就可以下载(Download - GeoServer),但是注意要到自己所安装的对应geoserver版本的下载页面中去下载插件,如果下载了其它版本下的插件,可能会在启动geoserver时导致报错。

由于我安装的是2.21.5版本的geoserver,因此就进入这个版本的下载页面。

然后下滑到下面的 Extensions 部分就可以找到对应的插件。

2.安装Vector Tiles插件

将下载的插件解压之后得到如下的这些文件,复制其中的.jar文件(jar包)。

将复制好的文件放到geoserver的 WEB-INF/lib目录下,这个目录中都是各种.jar文件,比较好辨认。

3.检查Vector Tiles插件是否安装成功

安装插件成功后重新启动GeoServer,然后打开GeoServer的图层页面

然后随便选择一个矢量图层

然后进入Tile Caching页面

滚动页面到 "Tile Image Formats" 部分,除了标准的GIF/PNG/JPEG格式之外,如果你好看到以下的内容就表示插件安装成功了。

4.发布矢量切片

发布矢量切片的过程也很简单,首先将自己准备的数据创建为一个矢量图层(具体的步骤我就不多说了)。

这里我就随便选择了一个GeoServer中的美国人口的矢量面图

点击进入“图层编辑”页面,然后再进入“Tile Caching”选项卡

滚动页面到 "Tile Image Formats" 部分,勾选application/json;type=geojsonapplication/json;type=topojsonapplication/vnd.mapbox-vector-tile这几个选项(注意:这三个选项对应了三种不同格式的矢量切片数据,我们每勾选一种就会GeoServer就会制作发布一种对应格式的矢量切片)

最后点击保存,一个带有矢量切片的图层就准备好了。

三、OpenLayers中加载矢量切片

1.加载TMS服务的矢量切片

网上绝大多数的文章中都是加载的TMS服务的矢量切片,这种加载方式看起来还是比较简单的,但是我在实际使用的时候却发现“困难重重”实际上用起来比较麻烦。

获取TMS服务地址

首先第一步我们要搞清楚我们所发布的矢量瓦片的TMS服务的url地址是什么。可以打开Goeserver去查看。

点击左上角的logo进入首页

在首页点击TMS的链接

进入的这个页面中就记录了目前我的这台GeoServer服务器上发布的所有TMS服务的url。

找到之前设置了矢量切片的图层的链接(例如我下面的这个美国人口的图层)

这其中以geojsontopojsonpbf结尾的url就是矢量切片的url。

然后就可以拿到一个类似于http://localhost:8080/geoserver/gwc/service/tms/1.0.0/topp%3Astates@EPSG%3A4326@geojson这样的url。这个url由几个部分组成:

  1. http://localhost:8080/geoserver/gwc/service/tms/1.0.0 这部分在同一个GeoServer服务器上是固定的。
  2. /topp%3Astates 这部分表示图层名,其中的%3A是冒号的URL编码形式,所以我这里的图层就叫做topp:states
  3. @EPSG%3A4326 这部分表示投影,我这使用的是 EPSG:4326
  4. @geojson 这部分表示数据的格式

一个完整的TMS的url还不止上面的这些,它应该还要包括切片的具体信息,例如:http://localhost:8080/geoserver/gwc/service/tms/1.0.0/topp%3Astates@EPSG%3A4326@geojson/3/2/1.geojson

后面的/3/2/1分别代表切片的层级、列号和行号。

加载TMS矢量切片的方式

OpenLayers中加载矢量切片都需要使用VectorTile 图层 + VectorTile数据源的方式来实现。

创建VectorTile 图层时可以通过style属性给请求到的矢量瓦片数据设置样式。

创建VectorTile数据源时则主要是要设置三个属性:

  1. url,即矢量瓦片服务的地址,由于OpenLayers中不支持直接请求TMS服务,所以需要通过请求XYZ服务的方式请求TMS,因此url要写成"http://localhost:8080/geoserver/gwc/service/tms/1.0.0/BeiJiang%3Abj@EPSG%3A4326@geojson/{z}/{x}/{-y}.geojson"的形式,也就是用占位符{z}{x}{y}来表示切片的层级、列号和行号。但是这里有一点特殊的地方在于使用的是{-y},之所以这样写是因为XYZ瓦片的y坐标从顶部开始向下递增,TMS瓦片的y坐标从底部开始向上递增,也就是说他们的y轴方向相反,所以url中要写成{-y}进行转换。
  2. format,这个属性是用来将矢量数据转换为Feature的,如果矢量切片的数据类型是geojson就使用GeoJSON转换器,如果矢量切片的数据类型是pbf就使用MVT转换器(这些转换器都是OpenLayers封装好的的都在ol/format目录下面)。
  3. tileGrid,这个属性应该用来定义切片规则的,具体怎么定义的我也搞不懂。这里由于我们使用的是加载XYZ服务的方式,所以可以直接使用OpenLayers内置的createXYZ函数来直接创建适合XYZ服务的tileGrid。但是在使用createXYZ函数时特别要注意,其中的extent属性默认为 EPSG:3857 投影的范围,如果你像我一样使用的是其它的投影(例如 EPSG:4326),那就必需要手动将这个属性设置为你所使用的投影的范围,否则请求的url将会404(不要问我是怎么知道的😭)
import { VectorTile as VectorTileLayer } from "ol/layer";
import { VectorTile as VectorTileSource } from "ol/source";
import { Style, Stroke, Circle as CircleStyle } from "ol/style";
import { GeoJSON} from "ol/format";
import { createXYZ } from "ol/tilegrid";// 加载矢量切片
const riverVectorTileLayer = new VectorTileLayer({source: new VectorTileSource({url: "http://localhost:8080/geoserver/gwc/service/tms/1.0.0/BeiJiang%3Abj@EPSG%3A4326@geojson/{z}/{x}/{-y}.geojson",format: new GeoJSON(),tileGrid: new createXYZ({extent: getProjection("EPSG:4326").getExtent(),maxZoom: 18,}),}),style: function (feature) {const style = new Style({stroke: new Stroke({color: "blue",width: 1,}),});return style;},
});
map.addLayer(riverVectorTileLayer);

但是我使用上面的这套代码进行加载却失败了,请求到的数据都是空的,这个就很奇怪,我看到很多的博客中都是这么写的,他们都能加载出来,我却加载不出来 ╭(╯^╰)╮。

之后,我历经了千辛万苦终于找到了原因出在哪里,我发现只要将层级z减一就能够请求到正确的矢量切片数据,这就说明XYZ的层级跟TMS的层级好像是不匹配的,有可能XYZ切片的层级是从1开始计数的,而TMS切片的层级是从0开始计数的。

想要将z减一,就需要使用tileUrlFunction属性,这个属性接收一个函数,然后XYZ切片的坐标就会作为参数传入到函数中,之后就可以根据XYZ切片的坐标来换算出TMS切片的坐标。

要换算的地方有两个,一是将z减一这个比较简单,二是要将y进行转换(因为它们的y轴是相反的)。

# 进行y轴转换的公式
y_tms = 最大行号 - y_xyz
# 因为
最大行号 = 总行数 - 1
# 所以
y_tms = 总行数 - 1 - y_xyz
# 又因为
总行数 = 2 ^ z_tms  # 2的z次方
z_tms = z_xyz - 1
# 所以最终的公式为
y_tms = (2 ^ (z_xyz - 1)) - 1  - y_xyz
import { VectorTile as VectorTileLayer } from "ol/layer";
import { VectorTile as VectorTileSource } from "ol/source";
import { Style, Stroke, Circle as CircleStyle } from "ol/style";
import { GeoJSON} from "ol/format";
import { createXYZ } from "ol/tilegrid";// 加载矢量切片
const riverVectorTileLayer = new VectorTileLayer({source: new VectorTileSource({tileUrlFunction: function (tileCoord) {const [z, x, y] = tileCoord;const url ="http://localhost:8080/geoserver/gwc/service/tms/1.0.0/BeiJiang%3Abj@EPSG%3A4326@geojson/" +(z - 1) +"/" +x +"/" +(Math.pow(2, z - 1) - 1 - y) +".geojson";return url;},format: new GeoJSON(),tileGrid: new createXYZ({extent: getProjection("EPSG:4326").getExtent(),maxZoom: 18,}),}),style: function (feature) {const style = new Style({stroke: new Stroke({color: "blue",width: 1,}),});return style;},
});
map.addLayer(riverVectorTileLayer);

通过上面的方式我最终才将我自己的矢量切片加载出来了

2.加载WMTS服务的矢量切片

之后我又看到了另一种加载方式,通过WMTS进行加载。WMTS加载起来那就比TMS要复杂很多了,但是其实是有一条“捷径”的。

在GeoServer中,进入切片图层页面。

选择一个带有矢量切片的矢量图层,在预览的下拉菜单中选择带有geojsontopojsonpbf的选项就可以预览矢量切片

然后F12打开调试工具查看页面的源代码,在demo文件夹中就可以找到通过WMTS加载矢量切片的代码,直接照抄就可以了。

// 通过WMTS加载矢量切片
function addRiver_vectorTileWMTS() {const baseUrl = "http://localhost:8080/geoserver/gwc/service/wmts";const layerName = "BeiJiang:bj";const style = "";const gridsetName = "EPSG:4326";const format = "application/json;type=geojson";const resolutions = [0.703125, 0.3515625, 0.17578125, 0.087890625, 0.0439453125, 0.02197265625,0.010986328125, 0.0054931640625, 0.00274658203125, 0.001373291015625,6.866455078125e-4, 3.4332275390625e-4, 1.71661376953125e-4,8.58306884765625e-5, 4.291534423828125e-5, 2.1457672119140625e-5,1.0728836059570312e-5, 5.364418029785156e-6, 2.682209014892578e-6,1.341104507446289e-6, 6.705522537231445e-7, 3.3527612686157227e-7,];const params = {REQUEST: "GetTile",SERVICE: "WMTS",VERSION: "1.0.0",LAYER: layerName,STYLE: style,TILEMATRIX: gridsetName + ":{z}",TILEMATRIXSET: gridsetName,FORMAT: format,TILECOL: "{x}",TILEROW: "{y}",};let url = baseUrl + "?";for (var param in params) {url = url + param + "=" + params[param] + "&";}url = url.slice(0, -1);riverVectorTileLayer = new VectorTileLayer({source: new VectorTileSource({url,format: new GeoJSON(),projection: "EPSG:4326",tileGrid: new WMTSTileGrid({extent: [-180, -90, 180, 90],resolutions: resolutions,matrixIds: ["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21",],}),}),style: function (feature) {const style = new Style({stroke: new Stroke({color: "blue",width: 1,}),});return style;},});map.addLayer(riverVectorTileLayer);
}

参考资料

  1. 矢量切片(Vector tile)_a vectortile source can only be rendered if it has-CSDN博客
  2. Openlayer加载geoserver发布的矢量切片_openlayers 加载geoserver xyz-CSDN博客
  3. QGIS加载Geoserver发布的矢量瓦片服务 - 槑孒 - 博客园
  4. 10openlayers加载矢量瓦片图层-CSDN博客
  5. GeoServer官方教程:矢量切片
  6. OpenLayers教程十:多源数据加载之瓦片地图原理二
  7. 【webgis】地图切片|矢量地图切片|栅格地图切片-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/76139.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

神经网络入门—自定义网络

网络模型 定义一个两层网络 import torch import torch.nn as nn import torch.optim as optim import torch.nn.functional as F# 定义神经网络模型 class Net(nn.Module):def __init__(self, init_x0.0):super().__init__()self.fc1 nn.Linear(1, 10)self.fc2 nn.Linear(…

无人机装调与测试

文章目录 前言一、无人机基本常识/预备知识(一)无人机飞行原理无人机硬件组成/各组件作用1.飞控2.GPS3.接收机4.电流计5.电调6.电机7.电池8.螺旋桨9.UBEC(稳压模块) (二)飞控硬件简介(三&#x…

2024年-全国大学生数学建模竞赛(CUMCM)试题速浏、分类及浅析

2024年-全国大学生数学建模竞赛(CUMCM)试题速浏、分类及浅析 全国大学生数学建模竞赛(China Undergraduate Mathematical Contest in Modeling)是国家教委高教司和中国工业与应用数学学会共同主办的面向全国大学生的群众性科技活动,目的在于激…

Linux入门指南:从零开始探索开源世界

🚀 前言 大家好!今天我们来聊一聊Linux这个神奇的操作系统~ 🤖 很多小伙伴可能觉得Linux是程序员专属,其实它早已渗透到我们生活的各个角落!本文将带你了解Linux的诞生故事、发行版选择攻略、应用领域,还有…

记录vscode连接不上wsl子系统下ubuntu18.04问题解决方法

记录vscode连接不上wsl子系统下ubuntu18.04问题解决方法 报错内容尝试第一次解决方法尝试第二次解决方法注意事项参考连接 报错内容 Unable to download server on client side: Error: Request downloadRequest failed unexpectedly without providing any details… Will tr…

Cursor+MCP学习记录

参考视频 Cursor MCP 王炸!彻底颠覆我的Cursor工作流,效率直接起飞_哔哩哔哩_bilibili 感觉这个博主讲的还不错 所使用到的网址 Smithery - Model Context Protocol Registry Introduction - Model Context Protocol 学习过程 Smithery - Model …

testflight上架ipa包-只有ipa包的情况下如何修改签名信息为苹果开发者账户对应的信息-ipa苹果包如何手动改签或者第三方工具改签-优雅草卓伊凡

testflight上架ipa包-只有ipa包的情况下如何修改签名信息为苹果开发者账户对应的信息-ipa苹果包如何手动改签或者第三方工具改签-优雅草卓伊凡 直接修改苹果IPA包的签名和打包信息并不是一个推荐的常规做法,因为这可能违反苹果的开发者条款,并且可能导致…

深入解析Java内存与缓存:从原理到实践优化

一、Java内存管理:JVM的核心机制 1. JVM内存模型全景图 ┌───────────────────────────────┐ │ JVM Memory │ ├─────────────┬─────────────────┤ │ Thread │ 共享…

紫光展锐5G SoC T8300:影像升级,「定格」美好世界

影像能力已成为当今衡量智能手机性能的重要标尺之一。随着消费者对手机摄影需求日益提升,手机厂商纷纷在影像硬件和算法上展开激烈竞争,力求为用户带来更加出色的拍摄体验。 紫光展锐专为全球主流用户打造的畅享影音和游戏体验的5G SoC——T8300&#x…

【Java设计模式】第6章 抽象工厂模式讲解

6. 抽象工厂模式 6.1 抽象工厂讲解 定义:提供一个接口创建一系列相关或依赖对象,无需指定具体类。核心概念: 产品等级结构:同一类型的不同产品(如Java视频、Python视频)。产品族:同一工厂生产的多个产品(如Java视频 + Java手记)。适用场景: 需要创建多个相关联的产品…

Dify教程01-Dify是什么、应用场景、如何安装

Dify教程01-Dify是什么、应用场景、如何安装 大家好,我是星哥,上篇文章讲了Coze、Dify、FastGPT、MaxKB 对比,今天就来学习如何搭建Dify。 Dify是什么 **Dify 是一款开源的大语言模型(LLM) 应用开发平台。**它融合了后端即服务&#xff08…

Java后端开发-面试总结(集结版)

第一个问题,在 Java 集合框架中,ArrayList和LinkedList有什么区别?在实际应用场景中,应该如何选择使用它们? ArrayList 基于数组,LinkedList 基于双向链表。 在查询方面 ArrayList 效率高,添加…

nslookup、dig、traceroute、ping 这些工具在解析域名时是否查询 DNS 服务器 或 本地 hosts 文件 的详细对比

host配置解析 127.0.0.1 example.comdig 测试,查询 DNS 服务器 nslookup测试,查询 DNS 服务器 traceroute测试,先读取本地 hosts 文件,再查询 DNS 服务器 ping测试,先读取本地 hosts 文件,再查询 DNS 服务…

文件上传、读取与包含漏洞解析及防御实战

一、漏洞概述 文件上传、读取和包含漏洞是Web安全中常见的高危风险点,攻击者可通过此类漏洞执行恶意代码、窃取敏感数据或直接控制服务器。其核心成因在于开发者未对用户输入内容进行充分验证或过滤,导致攻击者能够绕过安全机制,上传或执行…

STM32 的编程方式总结

🧱 按照“是否可独立工作”来分: 库/方式是否可独立使用是否依赖其他库说明寄存器裸写✅ 是❌ 无完全自主控制,无库依赖标准库(StdPeriph)✅ 是❌ 只依赖 CMSIS自成体系(F1专属),只…

Flutter命令行打包打不出ipa报错

Flutter打包ipa报错解决方案 在Flutter开发中,打包iOS应用时可能会遇到以下错误: error: exportArchive: The data couldn’t be read because it isn’ in the correct format. 或者 Encountered error while creating the IPA: error: exportArchive…

SQL Server常见问题的分类解析(一)

以下是SQL Server常见问题的分类解析,涵盖安装配置、性能优化、备份恢复、高可用性等核心场景,结合微软官方文档和社区实践整理而成(编号对应搜索结果来源): 一、安装与配置问题 安装失败:.NET Framework缺失解决方案:手动安装所需版本.NET Framework,以管理员身份运行…

Spring Boot 3.x 下 Spring Security 的执行流程、核心类和原理详解,结合用户描述的关键点展开说明,并以表格总结

以下是 Spring Boot 3.x 下 Spring Security 的执行流程、核心类和原理详解,结合用户描述的关键点展开说明,并以表格总结: 1. Spring Security 核心原理 Spring Security 通过 Filter 链 实现安全控制,其核心流程如下&#xff1a…

Vue:路由切换表格塌陷

目录 一、 出现场景二、 解决方案 一、 出现场景 当路由切换时&#xff0c;表格操作栏会出现行错乱、塌陷的问题 二、 解决方案 在组件重新被激活的时候刷新表格 <el-table ref"table"></el-table>activated(){this.$nextTick(() > {this.$refs[t…

文件上传漏洞原理学习

什么是文件上传漏洞 文件上传漏洞是指用户上传了一个可执行的脚本文件&#xff0c;并通过此脚本文件获得了执行服务器端命令的能力。“文件上传” 本身没有问题&#xff0c;有问题的是文件上传后&#xff0c;服务器怎么处理、解释文件。如果服务器的处理逻辑做的不够安全&#…