缘起
近期在项目中遇到这么一个需求,需要在地图上展示一组格网数据,格网大小为2m*2m
,地图api
用的mapboxgl
。起初拿到这个需要感觉很easy,在地图上添加一个fill
图层就好啦。把格网面数据添加到地图上之后,在大比例尺下显示正常,但是当地图层级小于15级时,渲染出的结果会消失。
[图片上传失败...(image-80612f-1664419487137)]
简单理一下原因,应该是在地图缩小后,每个网格所占的像素太小,所以就消失了。
mapboxgl
在处理symbol
图层的时候,会遇到点位自动避让问题,导致部分点位不显示。解决方法是把layout
中的icon-allow-overlap
设置为true
,这样就相当于关闭了自动避让功能,所有点图标保持可见状态。但是针对fill
图层却没有这么一个属性。
但是这种情况又需要查看数据,要如何实现呢?
首先分析下数据,我的原始数据是通过模型导出的tiff
格式的栅格数据,然后在后台根据tiff
格式数据中每个像素所在行列号以及其灰度值生成带属性的格网数据,其中像素的灰度值就是在渲染时需要分类展示的值。既然原始tiff
数据的灰度值就是所用的属性值,那是不是直接添加到地图就好了。接下来的解决方案就是看是否能直接用mapboxgl
直接加载tiff
数据,并渲染出自己想要的效果。
mapboxgl加载tiff
查看mapboxgl
文档,可以看到mapboxgl
支持image
图层,只需传入url和coordinates
// 添加至地图
map.addSource('some id', {
type: 'image',
url: 'https://www.mapbox.com/images/foo.png',
coordinates: [
[-76.54, 39.18],
[-76.52, 39.18],
[-76.52, 39.17],
[-76.54, 39.17]
]});
可是,当我把地址换成tiff
数据时却报错了。下面为报错内容:
Could not load image because of The source image could not be decoded.. Please make sure to use a supported image type such as PNG or JPEG. Note that SVGs are not supported
可以简单理解为不支持tiff
格式。
tiff文件解析
既然mapboxgl
的image
图层不支持tiff
格式,那是不是可以把tiff
数据导出成png
呢,于是使用arcmap
打开了tiff
数据,导出数据格式也支持png
,但是在保存时又报错了。
[图片上传失败...(image-abbad0-1664419487137)]
经过分析,发现是tiff
数据波段数量的原因,我的这份数据波段数为1,从网上下载了一份测试数据,波段数为3,可以成功导出。
[图片上传失败...(image-d0be83-1664419487137)]
在查找相关解决方案的时候,看到这么个工具,geotiff.js,可以通过js
解析tiff
数据并渲染,leaflet
有个扩展就是用的这个工具,https://github.com/stuartmatthews/leaflet-geotiff。查看geotiff.js
相关文档,发现其实用起来还是挺方便的,通过简单的代码实现的我的需求。
先使用geotiff.js
解析tiff
数据,再配合使用canvas
绘制图片导出base64
格式数据,然后就可以使用添加到mapboxgl
图层了。
核心代码如下:
async function getData() {
GeoTIFF.fromUrl(url).then(tiff => {
console.log(tiff)
getImage(tiff)
});
}
async function getImage(tiff) {
const image = await tiff.getImage();
let bbox = await image.getBoundingBox();
let data = await image.readRasters({
samples: rgbBands // 波段数量,一个波段:[0],三个波段:[2,1,0]
});
let base64Image = getBase64Image(data)
addToMapboxgl(base64Image)
}
function getBase64Image(data) {
let thumbnailPixelHeight = data.height
let thumbnailPixelWidth = data.width
let canvas = document.createElement('canvas')
canvas.width = thumbnailPixelWidth
canvas.height = thumbnailPixelHeight
let ctx = canvas.getContext("2d")
let totalPixelCount = 0
for (let y = 0; y < thumbnailPixelHeight; y++) {
for (let x = 0; x < thumbnailPixelWidth; x++) {
let colour = 'rgb(0, 0, 0, 0)' // let the default be no data (transparent)
// 根据灰度值所在范围渲染颜色
if (data[0][totalPixelCount] > 0) {
if (data[0][totalPixelCount] > 50 && data[0][totalPixelCount] <= 55) {
colour = `rgb(15, 255, 0, 1)`
} else if (data[0][totalPixelCount] > 55 && data[0][totalPixelCount] <= 60) {
colour = `rgb(155, 255, 0, 1)`
} else if (data[0][totalPixelCount] > 60 && data[0][totalPixelCount] <= 65) {
colour = `rgb(255, 255, 0, 1)`
} else {
colour = `rgb(255, 255, 0, 1)`
}
}
ctx.fillStyle = colour
ctx.fillRect(x, y, 1, 1)
totalPixelCount++
}
}
let canvasImage = canvas.toDataURL("image/png")
return canvasImage
}
// 将图片添加到地图
function addToMapboxgl(image) {
map.addSource('tiff-source', {
"type": "image",
"url": image,
"coordinates": [
[114.425597191307, 38.1091563484708],
[114.538187627939, 38.1091563484708],
[114.538187627939, 37.9627378349512],
[114.425597191307, 37.9627378349512]
]
});
map.addLayer({
id: 'tiff-layer',
'type': 'raster',
'source': 'tiff-source',
'paint': {
'raster-fade-duration': 0
}
});
}
本以为到这里问题已经解决,但是在查看地图时,发现图片图层数据叠加到底图有不小的偏移。
经过一番对比分析,发现原来是tiff
数据的坐标系与地图坐标系不一致的导致的。我拿到的tiff
数据坐标系为西安80的投影坐标系,在展示时配置的为wgs84
地理坐标系,所以会有偏差。既然是坐标系问题,那就通过工具对tiff
文件做下投影转换。这里用的是arcmap
,打开ArcToolbox–>Data Management Tools–>Projections and Transformations–>Raster–>Project Raster
[图片上传失败...(image-eff8eb-1664419487137)]
转换之后会发现,数据的行列值也会发生变化,也就是tiff
图片的大小和形状都有所变化。
转换前:
[图片上传失败...(image-cdfbd7-1664419487137)]
转换后:
[图片上传失败...(image-a35c8d-1664419487137)]
使用转换后的数据再次解析,然后叠加到地图,位置完全匹配。
最终展示方案
通过尝试发现,单独的图片展示时,由于图片分辨率固定,当地图等级放大到一定程度图片会被放大很多导致图片模糊不清,展示效果不理想;单独的格网面展示时,当地图等级缩小到一定程度,面图层则会消失,也就是文章开头提到的问题。
综上,根据自己的格网数据大小,判断在哪个等级格网面数据会消失,小于这个等级使用图片展示,大于这个等级用格网面展示,就可以完美的展示出想要的效果。
处理前效果:
[图片上传失败...(image-cb4fdb-1664419487137)]
处理后效果:
[图片上传失败...(image-b1e493-1664419487137)]
以上为有
tiff
栅格数据情况的解决方案,针对于只有格网面数据,而没有tiff
栅格数据的情况要怎么解决呢?如果在这组格网数据中,每个网格的属性中有他所在原始
tiff
数据的像素位置,以及原始tiff
数据像素大小,就可以写一个类似上文中的getBase64Image方法,遍历每个网格,在网格对应的像素位置上绘制颜色,然后再通过canvas
导出图片添加到地图。
总结
-
mapboxgl
的image
图层无法直接添加tiff栅格数据 -
mapboxgl
添加fill
图层时,地图层级缩小到一定程度,面数据所占像素值过小无法显示 -
tiff
数据可以使用geotiff.js+canvas
解析,得到base64
的图片,添加到mapboxgl
的image
图层 - 在解析
tiff
数据时,需注意它的坐标系、波段个数等信息 - 在做展示时可以
image
图层和fill
图层结合展示,效果较好
参考资料:
- https://geotiffjs.github.io/geotiff.js/
- https://github.com/stuartmatthews/leaflet-geotiff
- https://www.cnblogs.com/arxive/p/6746570.html
原文地址:http://gisarmory.xyz/blog/index.html?blog=mapboxgl-geotiff
欢迎关注《GIS兵器库》
本文章采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名《GIS兵器库》(包含链接: http://gisarmory.xyz/blog/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。