Cesium加载MVT数据
1.前言
MVT数据格式,就是Mapbox开发的一种矢量数据格式,可以自定义矢量数据样式,自定义丰富的地图风格。Cesium的标准是3dtile,主打的就是三维数据格式。关于Cesium加载Mvt格式的方法,其实网上并不是很多,而且Cesium对于支持MVT格式的热情也不是很高,主要的精力还是研究 3dtiles 格式。
- 1.cesium+mapbox,这个方法解析和加载的速度都很快,不过只支持3857和900913,不支持2000坐标系。
- 2.cesium+openlayer,利用openlayer的方法将mvt绘制到canvas上,然后传给cesium进行渲染,这个方法不限制坐标系,支持4490,4326,3857,900913等。
- 3.使用超图封装的cesium加载。
根据我的理解,其实使用cesium+openlayer的方式更加的舒服,虽然不如cesium+mapbox的性能更好,但是openlayer可以模块化开发,需要什么模块,只需要引入 render 就可以了,但是 mapbox 要想单独提取render,还需要使用自定义构建,比如 Mapbox-vector-tiles-basic-js-renderer 这个仓库。或者是在 两个 MVTImageryProvider 参考中,都需要一个 mapboxRenderer ,这个就是独立定制的了。
所以最终我决定改造 ol 版本的,因为 mars3d 提供的 ol 版本不是最新的,我想着先尝试修改一下。
1.Cesium 加载mapboxgl最新失量切片(mvt) 这篇借助于Openlayer进行了加载,和 mars3d 的加载方式差不多,里面也做了变换。
2.cesium加载geoserver发布的mvt服务
3.mars3d-link-supermap Mars3d中使用Supmap Cesium进行兼容的仓库。
5.mapbox 扩展
2.MVT格式
MVT图层包括以下几个主要的部分。
1.MVT: Mapbox Vector Tiles
2.矢量瓦片 extent说明:瓦片的extent只是指明了瓦片的范围,并不是瓦片的渲染之后的大小。例如一张extent为4096的矢量瓦片,并不意味着最终渲染出来的是一张4096 X 4096大小的图片。渲染出的图片大小并不由extent决定,通常会被渲染为256 X 256或512 X 512大小的图片。假设将一张extent为4096的矢量瓦片渲染为256 X 256大小的图片,那么(0, 0)~(16, 16)坐标范围内的点都将被综合到图片上的(0, 0)像素点。
3.矢量瓦片标准 图层必须包含一个extent字段,表示瓦片的宽度和高度,以整数表示。矢量瓦片中的几何坐标可以超出extent定义的范围。超出extent范围的几何要素被经常用来作为缓冲区,以渲染重叠在多块相邻瓦片上的要素。
4.浅谈Mapbox开源技术
5.02. 矢量瓦片并行构建与分布式存储模型研究
6.python爬取pbf格式的矢量瓦片并转换为shp使用 这篇文章用python下载了pbf的格式文件,并进行了拼接。
3.瓦片原理
瓦片原理,就是通过一张张图片进行贴图,但是不同的坐标系有不同的计算方法。
1.WMTS服务及地图瓦片原理
2.瓦片(Tile)行列号计算方法
3.GIS开发:切片格式说明(翻译)-wgs84 wgs84的坐标系范围是:[-180,-90,180,90],单位是度。gdal2tiles地图切片
4. WMTS地图服务每一层级分辨率 这里没有给出计算公式,直接列举了 3857 和 4326 坐标系的瓦片分辨率
1.Cesium
经过不断的学习和探索,还是没有明白地图瓦片的原理,但是在 UrlTemplateImageryProvider.js 中,有这么一段代码
1 | const longitudeLatitudeProjectedScratch = new Cartesian3(); |
1.Cesium原理篇:5最长的一帧之影像
2. Cesium笔记(3):基本控件简介—ImageryProvider地图瓦片地图配 这里讲了多种 provider 的用法,每一个Provider都对应一个tilingScheme,支持经纬度和墨卡托两种投影方式,默认是全球范围,用户也可以指定其范围。
3.cesium加载坐标系为4490的arcgis瓦片图 这里有几个地方,一个就是 _tilingScheme,一个就是 GeographicTilingScheme.prototype.getNumberOfXTilesAtLevel
4.cesium加载arcgis离线松散瓦片(EPSG:4326, EPSG:4490) 这里使用Cesium.UrlTemplateImageryProvider自定义瓦片规则进行加载。重写了 customTags 和 tilingScheme 。
5.ImageryLayerCollection类 Cesium.ImageryProvider类及其子类封装了加载各种影像图层的方法,其中Cesium.ImageryProvider类是抽象类、基类或者可将其理解为接口,它不能被直接实例化。上图中Cesium.ImageryLayer的第一个参数就是需要的图层数据,我们可以把ImageryProvider看作是影像图层的数据源(包裹在ImageryLayer类内部),我们想使用哪种影像图层数据或服务就用对应的ImageryProvider子类去加载
6.Cesium 4490 解决方案 2000.xml 和2000天地图切片方案(5-16级).xml 区别在于2000有21级切片,2000天地图只有20级切片,2000的切片方案第0级是多出来的一级,其他级别与2000天地图一致。1.修改Cesium 源码WebMapTileServiceImageryProvider,判断WMTS服务的切片级别,请求数据时调整级别,这个方案比较麻烦,对源码的理解要比较深入,才能修改,暂时没用这种方式。2.修改数据。3.互联网2000的切片方案,原点是400,-400,似乎也加载不了,有数据的时候可以测试下。
OpenLayer
使用 openlayer 进行 mvt 的渲染,在官网上也有很多的例子。
1.openlayers6:入门基础(二)之加载图层 地图容器(Map)与图层(Layer)的渲染有Canvas、WebGL两种类型,分别由ol.renderer.Map与ol.renderer.Layer实现。
2.openlayers3瓦片加载的源码浅析与小结 这里有瓦片渲染的原理解析。getTileRangeForExtentAndResolution获取可视范围内的瓦片范围;calculateTileRanges->循环遍历resolutions,调用 getTileRangeForExtentAndZ,根据extent计算瓦片范围;getTileRangeForExtentAndResolution计算瓦片的范围。这里还有TMS瓦片、WMTS瓦片加载、百度地图瓦片、腾讯地图瓦片
3.OpenLayers教程十:多源数据加载之瓦片地图原理二
4.OpenLayers6学习笔记(三)—— 瓦片地图基础知识 这篇文章我觉得写的挺好的,还有TileDebug,比例尺=0.0254÷(分辨率×96)。在WebGIS中使用的在线瓦片地图采用Web墨卡托(Mercator)投影坐标系(OpenLayers默认使用EPSG:3857),经过投影后整个地球是一个正方形,范围为经度[-180°, 180°],纬度[-85°, 85°],单位为度。对应的Web墨卡托投影坐标系的范围为x[-20037508.3427892, 20037508.3427892]、y[-20037508.3427892, 20037508.3427892],x、y方向上的各层级瓦片地图分辨率计算公式可以归纳为:resolution = rang÷(256×2^z).
4.插件封装
插件的封装,就是用符合 Cesium 的标准进行的二次开发,比如如果要自定义 ImageryProvider, 可以查看官方的代码 但是这个官方的代码,没有使用es6模块进行类的定义,还在使用使用 ES5 的方式进行类的扩展。
1.Cesium深入浅出之插件封装
5.样式解析
可以使用 openlayers / ol-mapbox-style 这个库进行mapbox的样式解析,但是这个只适用于 openlayer 。
(1) 而且无法解析 mapbox style 中的 glyphs 属性。“ol-mapbox-style cannot use PBF/SDF glyphs for text-font layout property, as defined in the Mapbox Style specification. Instead, it relies on web fonts. A ol:webfonts metadata property can be set on the root of the Style object to specify a location for webfonts, e.g.”
(2) stylefunction 函数中的 spriteData 参数无效,必须要通过 spriteimageUrl 指定精灵图的地址,才能使 精灵图生效。styleFunction 会下载图片,并调用 oenlayer 图层的 change 方法修改图层。
6.加载示例
下面是我研究的几个源码,最后我选择了着重研究 cesium + openlayer
cesium-mvtimageryprovider
mvt-imagery-provider
hongfaqiu / MVTImageryProvider 这是另外的一个MVTImageProvider的改造
mars3d-PbfLayer
mars3d-mapbox 这个地方是 mars3d 改造的 mapbox-gl 的例子,但是没有运行起来。我查了下资料,找到了landtechnologies / Mapbox-vector-tiles-basic-js-renderer,也就是所有基于mapbox在cesium中渲染mvt的,都需要引入这个库。这里主要就是 重载了 src/basic/renderer.js 这个代码,实现了独立渲染。
1.Cesium加载mapbox矢量瓦片源码实测可用 这里也用了一个 Mapbox.BasicRender 的东西,其实是一个库。
mars3d-PbfolLayer
pbf-ol 这里借助于 openlayer 实现的 pbf 加载。但是我查看了源码之后,发现有些方法在最新的openlayer中并不存在,比如 ol.render.canvas.ReplayGroup 这个方法,只在 5.1.3 版本中存在。
(1) openlayer的styleFunction,渲染的是一个openlayer图层,而不是一个feature,所以不能使用 ol-mapbox-style 这个库。
后来我还是极力的把这个库给用上了。
(2) 重写 ImageProvider 的 requestImage 方法,获取每一个切片。 ImageProvider是一个虚基类,所有的 Provider 都是重写了这个类。ImageryLayer.js 源码中调用了 ImageProvider的requestImage方法。ImageryLayer.prototype._requestImagery 实现了 doRequest()、success()、和 failure() 方法,分别实现了请求切片,和请求切片失败时的处理。
函数中需要一个 imager 参数,路径在 engine/Source/Scene/Imagery.js,可以通过这个类,继续的深入的探讨关于如何绘制一个瓦片的过程。
(3) 然后使用 openlayer 提供的方法,将其渲染为一个 canvas 并返回,这里同样有两种方法,比较简单的一种就是使用 ol.render.toContext方法。
1 | var canvas = document.createElement("canvas") |
这里同样有一个问题,就是读取的数据如何绘制到一个没有坐标系的 cavas 上。 后来我经过尝试,发现这个 canvas
1.toContext Binds a Canvas Immediate API to a canvas context, to allow drawing geometries to the context’s canvas. 我发现了这个函数更加的简单,就是把一个feature,渲染到一个 canvas 上面
2.OpenLayers官方示例详解十一之在自定义canvas元素上渲染OpenLayers的几何图形(Render geometries to a canvas)
(4) 这里需要有一个地方注意,就是在使用 format.MVT 进行转换的时候,还有一个坐标系的问题。
1 | var features = that._mvtParser.readFeatures(arrayBuffer,{ |
我经过大量的实验,发现在创建 canvas 的时候,使用宽高 4096 可以基本解决我的问题。
(5) 还有一种方法,就是使用 openlayer 源码中提供的方法,这个具体的原理还是有些厉害的,VectorTileLayer.js,但是比较复杂,我看到的多数的目前的代码,都是以这种形式展开的。我也尝试过,目前是可以的。
1 |
|
1.ol5 ReplayGroup 这里遇到了一样的问题。
2.Openlayers 3 - How to garbage collect Canvas ReplayGroup objects?
3.Static Image is not cleared when change projection 这里有 resolutionsFromExtent 的源码,可以借鉴。
4.Openlayers源码阅读(八):要素Feature渲染过程 这里讲了openlayer渲染feature的过程。
问题
(1) missing required property “version”
除了上面的问题,还有就是 ”missing required property ‘layers’“、”missing required property ‘sources’“,都是一起冒出来的
(2) requestImage 请求切片z不正确
在我使用代码调用在重写 requestImage 方法的时候,我请求的是4490的坐标系,这个level必须要在原有基础上进行 +2 处理,这个操作让我始终无法理解,但是我最后还是实验出来了这个方法,算是瞎猫碰上了死耗子。因为我对比了使用mapbox加载矢量切片的路径,还有使用Cesium进行矢量切片的路径,发现7级、8级的时候,切片的x和y总是非常的大,这让我非常的郁闷。后来我干脆就直接输出了在地图上绘制了瓦片信息,进行问题查找。我在这个函数中进行了方里网格的绘制,代码如下。
1 | MVTImageryProvider.prototype.requestImage = function (x, y, level, request) { |
1.Cesium 之加载ArcGIS Server 4490切片服务(含orgin -400 400) ArcGisMapServerImageryProvider直接加载CGCS2000的4490坐标系,虽然可以使用WebMapTileServiceImageryProvider加载符合OGC标准的WMTS类型,但只能针对于原点在orgin X: -180.0Y: 90.0的情况,对于原点在orgin X: -400.0Y: 400.0时还是无法正常加载,为了能够实现加载,以下通过修改源码方式来达到目的。
2.Cesium加载矢量数据探索——从geojson到矢量切片 目中提供的mapbox-gl并不是真正的mapbox项目中的mapbox-gl.js文件,而是由另一位大神,根据mapbox源码中的渲染方式进行改进,从而可以实现基于mapbox的方式加载矢量切片的图层,该项目最终打包命名为mapbox-gl.js
(3) loseContext: context lost
通过自定义 imageryprovider,然后添加到 layers中,直接将内存撑爆了:Out of Memory。下面的代码看起来没有毛病。
1 | const provider = new MvtImageryProvider({ |
【解决方案】
这个问题也是让我非常的郁闷,等我关了电脑,第二天放弃使用 ToDesk 远程桌面进行远程开发之后,这个问题就没有出现了。另外非常的郁闷,上一次也是出现了这个问题,vscode 总是崩溃,而且远程调试的时候,无论是chrome还是firefox,都会出现问题。
这个问题我记得我上一个星期在进行远程开发的时候也遇到过了,《{ post_link Tensorflow实战之常见问题 Tensorflow实战之常见问题 }》这篇文章中也出现的类似的现象,就是vscode总是崩溃,总是显示OOM,关闭之后,重启也没有用,解决方法也是类似,我头天晚上关机,关了远程,然后第二天到电脑实地进行训练,结果不再出现这个问了,真的非常的令人头疼。
1.Cesium学习之添加基本图层(adding Imagery Layers))
2.cesium 解决 WebGLRenderingContext 丢失问题 一般情况下,一个 viewer 对应着一个 webgl context,如果有多个就会存在多个 WebGLRenderingContext。
3.Cesium losing webgl context in Chrome