
本文字数:10340;估计阅读时间:26 分钟
审校:庄晓东(魏庄)
本文在公众号【ClickHouseInc】首发

Meetup活动
ClickHouse 上海首届 Meetup 讲师招募中,欢迎讲师在文末扫码报名!
也许你已经听说过 Flight Radar,这个实时航班跟踪地图,跟踪飞机在天空中飞行非常有趣,但在这篇博客文章中,我们将向你介绍更酷炫的东西!!
ADS-B(自动相关监视广播)是一种用于广播各种航班数据的无线电协议。我们的联合创始人兼首席技术官 Alexey Milovidov 基于这些数据打造了一个交互式可视化和分析工具。他在这个过程中创造了一种全新的艺术形式!

曼哈顿上空的直升机
这个网站聚合并可视化了大量的航空交通数据。这些数据存储在 ClickHouse 数据库中,并可以实时查询。你可以使用自定义的 SQL 查询调整可视化效果,并从 50 亿条记录中钻取到单个数据记录。结果是一些非常壮观的图像!
虽然这篇博客文章主要关注演示是如何构建的,但你也可以直接跳到结尾查看一些惊人的视觉效果。
或者,访问 https://adsb.exposed,找到你所在城市的可视化效果,并在社交媒体上分享!我们将为最佳图像赠送一个 ClickHouse T 恤,并将在我们下次的社区通话中于 4 月 30 日公布获胜者。
这个演示的完整源代码可以在这里找到【https://github.com/ClickHouse/adsb.exposed?tab=readme-ov-file】。
如何获取 ADS-B 数据?
ADS-B 数据是由安装在飞机上的“应答机”发送的。这种协议没有加密,对于数据的收集、使用和重新分发没有任何限制。大多数民航飞机都会发送这些数据,而在某些国家,甚至滑翔机、无人机和机场地面车辆也会如此。但对于军用飞机和私人轻型飞机,是否发送这些数据就不确定了。
你可以通过自己的无线电接收器(比如 SDR)在空中收集这些数据,不过你的接收器只能在一定范围内接收到数据。此外,也有一些平台可以用来分享和交换这些数据。一些平台邀请用户共享数据,但对于商业使用则可能受限。虽然飞机发送的数据基本上属于公共领域,但公司可以从这些数据中制作和许可衍生作品。
我们使用了两个数据来源:ADSB.lol(提供完整的历史数据,每天 3000 到 5000 万条记录,自 2023 年以来根据开放数据库许可证提供)和 ADSB-Exchange(仅提供每个月第一天的数据样本,每天约 12 亿条记录,覆盖范围更广)。
另一个有前景的数据来源是 airplanes.live,该作者提供了用于非商业用途的历史和实时数据。这个平台的覆盖范围和数据质量都很好,我们计划在接下来的几天内将其纳入使用。
实现细节
该网站采用单个 HTML 页面实现,没有使用 JavaScript 框架,并且源代码未经过压缩,因此易于阅读。
地图渲染
我们使用 Leaflet 库来在两个图层中显示地图。背景图层使用 OpenStreetMap 的瓦片创建了典型的地理地图。而主要图层则将可视化效果叠加在背景地图上。
为了实现可视化效果,我们使用了 GridLayer 和自定义回调函数 createTile,该函数会在需要时动态生成 Canvas 元素:
L.GridLayer.ClickHouse = L.GridLayer.extend({
createTile: function(coords, done) {
let tile = L.DomUtil.create('canvas', 'leaflet-tile');
tile.width = 1024;
tile.height = 1024;
render(this.options.table, this.options.priority, coords, tile).then(err => done(err, tile));
return tile;
}
});
const layer_options = {
tileSize: 1024,
minZoom: 2,
maxZoom: 19,
minNativeZoom: 2,
maxNativeZoom: 16,
attribution: '(c) Alexey Milovidov, ClickHouse, Inc. (data: adsb.lol, adsbexchange.com)'
};
为了减少对数据库的请求次数,每个瓦片都具有 1024x1024 的高分辨率。渲染函数使用 JavaScript 的 fetch 函数通过 ClickHouse 的 HTTP API 发送请求:
const query_id = `${uuid}-${query_sequence_num}-${table}-${coords.z - 2}-${coords.x}-${coords.y}`;
const hosts = getHosts(key);
const url = host => `${host}/?user=website&default_format=RowBinary` +
`&query_id=${query_id}&replace_running_query=1` +
`¶m_table=${table}¶m_sampling=${[0, 100, 10, 1][priority]}` +
`¶m_z=${coords.z - 2}¶m_x=${coords.x}¶m_y=${coords.y}`;
progress_update_period = 1;
const response = await Promise.race(hosts.map(host => fetch(url(host), { meth

最低0.47元/天 解锁文章
2万+

被折叠的 条评论
为什么被折叠?



