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

news2025/5/13 2:02:46

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

一、什么是矢量切片

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

矢量切片的特点

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

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

二、发布矢量切片服务

如何发布矢量瓦片服务,具体要看你使用的是什么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.coloradmin.cn/o/2332107.html

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

相关文章

AI智算-K8s+vLLM Ray:DeepSeek-r1 671B 满血版分布式推理部署实践

K8s + vLLM & Ray:DeepSeek-r1 671B 满血版分布式推理部署实践 前言环境准备1. 模型下载2. 软硬件环境介绍正式部署1. 模型切分2. 整体部署架构3. 安装 LeaderWorkerSet4. 通过 LWS 部署DeepSeek-r1模型5. 查看显存使用率6. 服务对外暴露7. 测试调用API7.1 通过 curl7.2 通…

深入浅出SPI通信协议与STM32实战应用(W25Q128驱动)(实战部分)

1. W25Q128简介 W25Q128 是Winbond推出的128M-bit(16MB)SPI接口Flash存储器,支持标准SPI、Dual-SPI和Quad-SPI模式。关键特性: 工作电压:2.7V~3.6V分页结构:256页/块,每块16KB,共1…

前端知识点---闭包(javascript)

文章目录 1.怎么理解闭包?2.闭包的特点3.闭包的作用?4 闭包注意事项&#xff1a;5 形象理解6 闭包的应用 1.怎么理解闭包? 函数里面包着另一个函数&#xff0c;并且内部函数可以访问外部函数的变量。 <script> function box() {//周围状态&#xff08;外部函数中定义的…

Java 泛型的逆变与协变:深入理解类型安全与灵活性

泛型是 Java 中强大的特性之一&#xff0c;它提供了类型安全的集合操作。然而&#xff0c;泛型的类型关系&#xff08;如逆变与协变&#xff09;常常让人感到困惑。 本文将深入探讨 Java 泛型中的逆变与协变&#xff0c;帮助你更好地理解其原理和应用场景。 一、什么是协变与…

多线程(进阶)(内涵面试题)

目录 一、常见的锁策略 1. 悲观锁 vs 乐观锁 2. 重量级锁 vs 轻量级锁 3. 挂起等待锁 vs 自旋锁 4. 普通互斥锁 vs 读写锁 5. 可重入锁 vs 不可重入锁 6. 公平锁 vs 非公平锁 7. 面试题 二、synchronized的原理 1. 基本特点 2. 加锁工作过程 1&#xff09;偏向锁&am…

蓝桥杯补题

方法技巧&#xff1a; 1.进行循环暴力骗分&#xff0c;然后每一层的初始进行判断&#xff0c;如果已经不满足题意了&#xff0c;那么久直接continue&#xff0c;后面的循环就不用浪费时间了。我们可以把题目所给的等式&#xff0c;比如说有四个未知量&#xff0c;那么我们可以用…

【MySQL篇】mysqlpump和mysqldump参数区别总汇

&#x1f4ab;《博主主页》&#xff1a;奈斯DB-CSDN博客 &#x1f525;《擅长领域》&#xff1a;擅长阿里云AnalyticDB for MySQL(分布式数据仓库)、Oracle、MySQL、Linux、prometheus监控&#xff1b;并对SQLserver、NoSQL(MongoDB)有了解 &#x1f496;如果觉得文章对你有所帮…

SQL:DDL(数据定义语言)和DML(数据操作语言)

目录 什么是SQL&#xff1f; 1. DDL&#xff08;Data Definition Language&#xff0c;数据定义语言&#xff09; 2. DML&#xff08;Data Manipulation Language&#xff0c;数据操作语言&#xff09; DDL和DML的区别 什么是SQL&#xff1f; SQL&#xff08;Structured …

神舟平板电脑怎么样?平板电脑能当电脑用吗?

在如今的数码产品市场上&#xff0c;神舟平板电脑会拥有独特的优势&#xff0c;其中比较受到大家关注的就是神舟PCpad为例&#xff0c;无论是设计还是规格也会有很多的亮点&#xff0c;那么是不是可以直接当成电脑一起来使用呢&#xff1f; 这款平板电脑就会配备10.1英寸显示屏…

【力扣hot100题】(075)数据流的中位数

一开始只建立了一个优先队列&#xff0c;每次查询中位数时都要遍历一遍于是喜提时间超限&#xff0c;看了答案才恍然大悟原来还有这么聪明的办法。 方法是建立两个优先队列&#xff0c;一个大根堆一个小根堆&#xff0c;大根堆记录较小的数&#xff0c;小根堆记录较大的数。 …

Java——pdf增加水印

文章目录 前言方式一 itextpdf项目依赖引入编写PDF添加水印工具类测试效果展示 方式二 pdfbox依赖引入编写实现类效果展示 扩展1、将inputstream流信息添加水印并导出zip2、部署出现找不到指定字体文件 资料参考 前言 近期为了知识库文件导出&#xff0c;文件数据安全处理&…

leetcode_19. 删除链表的倒数第 N 个结点_java

19. 删除链表的倒数第 N 个结点https://leetcode.cn/problems/remove-nth-node-from-end-of-list/ 1、题目 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#…

41、web前端开发之Vue3保姆教程(五 实战案例)

一、项目简介和需求概述 1、项目目标 1.能够基于Vue3创建项目 2.能够基本Vue3相关的技术栈进行项目开发 3.能够使用Vue的第三方组件进行项目开发 4.能够理解前后端分离的开发模式 2、项目概述 使用Vue3结合ElementPlus,ECharts工具实现后台管理系统页面,包含登录功能,…

zsh: command not found: hdc - 鸿蒙 HarmonyOS Next

终端中执行 hdc 命令抛出如下错误; zsh: command not found: hdc 解决办法 首先,查找到 DevEco-Studio 的 toolchains 目录路径; 其次,按照类似如下的文件夹层级结果推理到 toolchains 子级路径下,其中 sdk 后一级的路径可能会存在差异,以实际本地路径结构为主,直至找到 ope…

蓝桥杯--寻找整数

题解 public static void main(String[] args) {int[] mod {0, 0, 1, 2, 1, 4, 5, 4, 1, 2, 9, 0, 5, 10, 11, 14, 9, 0, 11, 18, 9, 11, 11, 15, 17, 9, 23, 20, 25, 16, 29, 27, 25, 11, 17, 4, 29, 22, 37, 23, 9, 1, 11, 11, 33, 29, 15, 5, 41, 46};long t lcm(2, 3);lo…

自然语言处理入门6——RNN生成文本

一、文本生成 我们在前面的文章中介绍了LSTM&#xff0c;根据输入时序数据可以输出下一个可能性最高的数据&#xff0c;如果应用在文字上&#xff0c;就是根据输入的文字&#xff0c;可以预测下一个可能性最高的文字。利用这个特点&#xff0c;我们可以用LSTM来生成文本。输入…

FPGA_DDR错误总结

1otp 31-67 解决 端口没连接 必须赋值&#xff1b; 2.PLACE 30-58 TERM PLINITCALIBZ这里有问题 在顶层输出但是没有管脚约束报错 3.ERROR: [Place 30-675] 这是时钟不匹配IBUF不在同一个时钟域&#xff0c;时钟不在同一个时钟域里&#xff0c;推荐的不建议修改 问题 原本…

NOIP2011提高组.玛雅游戏

目录 题目算法标签: 模拟, 搜索, d f s dfs dfs, 剪枝优化思路*详细注释版代码精简注释版代码 题目 185. 玛雅游戏 算法标签: 模拟, 搜索, d f s dfs dfs, 剪枝优化 思路 可行性剪枝 如果某个颜色的格子数量少于 3 3 3一定无解因为要求字典序最小, 因此当一个格子左边有…

基于ssm框架的校园代购服务订单管理系统【附源码】

1、系统框架 1.1、项目所用到技术&#xff1a; javaee项目 Spring&#xff0c;springMVC&#xff0c;mybatis&#xff0c;mvc&#xff0c;vue&#xff0c;maven项目。 1.2、项目用到的环境&#xff1a; 数据库 &#xff1a;mysql5.X、mysql8.X都可以jdk1.8tomcat8 及以上开发…

【10】数据结构的矩阵与广义表篇章

目录标题 二维以上矩阵矩阵存储方式行序优先存储列序优先存储 特殊矩阵对称矩阵稀疏矩阵三元组方式存储稀疏矩阵的实现三元组初始化稀疏矩阵的初始化稀疏矩阵的创建展示当前稀疏矩阵稀疏矩阵的转置 三元组稀疏矩阵的调试与总代码十字链表方式存储稀疏矩阵的实现十字链表数据标签…