谈起墨迹天气,想必人人都不陌生,除了展示空气质量指数(AQI)等天气相关信息,人们最爱它的短时临近预报。该预报精准度能够达到分钟级别,给咱们的出行带来了极大的便利。android
短时临近预报以雷达图像的形式展示,却不知,雷达数据是最难在原生 App 中快速呈现的数据之一。墨迹天气却克服了这个挑战,使用 Mapbox GL 实现了在 Mapbox 中国街景地图(Mapbox Streets Chinese)的快速、流畅、高分辨率的可视化效果,并上线 Android 和 iOS App 中。ios
下面咱们邀请到 Mapbox 中国解决方案工程师 Macro Shen,带你们详细剖析一下技术细节吧!git
来自雷达源的数据格式一般是栅格图像(Raster Image),在对这类数据进行可视化的时候,常见的方法是在地图上添加多个图像做为栅格源,或者生成图像图块并设置每帧显示不一样的图像实现具备延时效果的数据。github
许多天气 App 都使用相似的办法来显示雷达数据,包括墨迹天气的历史版本。可是这样的方案有下面两个主要缺点:npm
那么 Mapbox 为墨迹天气提供了怎样的解决方案呢?json
Mapbox 提供了矢量瓦片的解决方案,使数据密集型地图可以快速渲染。由于 Mapbox 的开源特性,这个过程具有了高度的设计灵活性。api
那么矢量瓦片为何会有优点呢?浏览器
根据 Mapbox 文档所述,这些矢量瓦片数据与用于 Web 端展现的图像是等效的,可是在缓存,缩放和快速呈现地图图像方面具备优点。矢量瓦片以紧凑的结构化格式、包含了几何与元数据,好比道路名称、地名、门牌号等,只有在客户端(好比 Web 浏览器或者移动 App)请求的时候,才会呈现矢量切片。渲染过程发生在客户端(Mapbox GL JS, Mapbox iOS SDK , Mapbox Android SDK)或者动态服务器(map API)上。缓存
相对于完整渲染的栅格图像,矢量瓦片有下面的两大优点:服务器
既然如此,咱们能够经过矢量瓦片可视化雷达数据。
首先,咱们须要将栅格图像转换为矢量格式(GeoJSON),而后生成矢量瓦片。
有不少的开源工具可以矢量化栅格图像。好比 d3-contour 能够从 Node.js 环境中的图像生成轮廓多边形,或者在 Python 环境中使用 numpy / rasterio 从图像生成像素多边形。
这是一个使用 d3-contour 的示例代码:
const contours = require('d3-contour').contours;
let w = 1100;
let h = 900;
let polygons = contours()
.size([w, h])
.thresholds(smoothValues)
(pixel_data);
let resultgeojson = {
type: 'FeatureCollection',
features: []
};
polygons.forEach((polygon) => {
//...
resultgeojson.features.push({
type: 'Feature',
properties: {
value: polygon.value,
idx: 0
},
geometry: {
type: 'Polygon',
coordinates: polygon.coordinates
}
});
});
复制代码
除了矢量化以外,咱们还须要在生成 GeoJSON 数据以前将坐标系从像素转换为地理坐标系。
const proj4 = require('proj4');
let pixel_position = [10, 20];
let geo_position = proj4('EPSG:3857', 'EPSG:4326',[
minLng + (maxLng - minLng) * (p[0] / w),
maxLat - (maxLat - minLat) * (p[1] / h)
]);
复制代码
传统的雷达图像帧与帧之间的跳动很是明显,为了更加平滑的动画效果,咱们须要提升数据帧的频率,所以,咱们尝试建立中间雷达图像(intermediate radar images)以提升帧率,得到更流畅的动画效果。而后,咱们运行处理脚本并生成更多向量,动画就能够以更多的步骤运行,看起来更流畅。
最简单的方法是在相邻帧之间插入数据,以下代码。
let current_data = [...];//An h * w array from current step of radar image
let next_data = [...];//An h * w array from next step of radar image
let new_data = [];
for (let i = 0; i < h; i++) {
for (let j = 0; j < w; j++) {
new_data[i * w + j] = parseInt((current_data[i * w + j] + next_data[i * w + j]) / 2);
}
}
let polygons = contours()
.size([w, h])
.thresholds(smoothValues)
(new_data);
复制代码
请注意,咱们在原始帧之间建立了不止一个过渡帧,以实现更流畅的动画,但代价就是数据大小会增长,这是须要权衡的。
咱们这里使用 tippecanoe 建立矢量瓦片,这个工具很是强大,能从大量的 GeoJSON、Geobuf 或 CSV 特征集合中建立矢量瓦片。
在 Mac OSX 系统上安装 tippecanoe 最简单的办法就是在 Terminal(终端)中输入下面的代码。
$ brew install tippecanoe
复制代码
安装好 tippecanoe 以后,咱们用下面的代码来生成 mbtiles(一种 OSM 中的数据格式,能够在一个单独的文件中存放地图切片)。
$ tippecanoe -o output.mbtiles -zg --drop-densest-as-needed radar.geojso
复制代码
请注意,-e 可用于将 tile 写入指定的目录而不是 mbtiles 文件,想要将矢量瓦片发布到 Web 服务的开发者可使用这个功能。
用于 Web 端的 Mapbox GL JS 和用于移动端的 Mapbox Maps SDK for iOS 和 Android 能够加载雷达矢量瓦片,并将其可视化。
下面是一个使用 Mapbox GL JS 可视化的源码,也能够参考一个使用 Maps SDK for Android 的案例。
map.addSource('radar-data', {
type: 'vector',
url: 'mapbox://examples.dwtmhwpu'
});
map.addLayer({
"id": "radarpolygon",
"type": "fill",
"source": "raddar-data",
"source-layer": "0",
"filter": ["==", "idx", 0],
'layout': {
"visibility": "visible"
},
'paint': {
'fill-opacity': 0.7,
'fill-color': [
"step",
["get", "value"],
"hsl(0, 0%, 100%)", 8,
"hsl(202, 88%, 51%)", 18,
"hsl(194, 88%, 51%)", 36,
"hsl(185, 88%, 51%)", 54,
"hsl(177, 96%, 53%)", 72,
"hsl(157, 96%, 53%)", 90,
"hsl(101, 94%, 65%)", 108,
"hsl(60, 100%, 49%)", 126,
"hsl(43, 100%, 49%)", 144,
"hsl(26, 100%, 49%)", 162,
"hsl(10, 100%, 49%)", 180,
"hsl(0, 64%, 43%)", 198,
"hsl(326, 47%, 29%)", 216,
"hsl(274, 47%, 29%)", 234,
"hsl(246, 56%, 35%)"
]
}
}, 'place-village');
复制代码
最后,咱们使用 Mapbox GL 的 setFilter 功能实现雷达数据的平滑动画效果。
let theinterval = setInterval(function() {
map.setFilter('radarpolygon', ['==', 'idx', currentidx]);
}, 500);
复制代码
下面就是输出结果,具备更好的用户体验,相对于传统可视化,实现了更加平滑、分辨率更高的渲染效果。
这就是矢量瓦片的魅力,不只仅是雷达图,也会有不少相似的图像展现能够采用这样的方法,找一个时间试试看?若是你有更多商务方面的问题,欢迎经过 Mapbox 微信公众号(Mapbox_China)联系咱们,关注回复【技术】便可。