使用 ClickHouse 可视化 Foursquare 地点数据

图片

本文字数:5939;估计阅读时间:15 分钟

作者:Alexey Milovidov

本文在公众号【ClickHouselnc】首发

图片

最近我们公司举行了一次线下全员活动,来自各地的同事齐聚一堂。每逢这样的聚会,我都会组织一次 hackathon。本次的主题是数据可视化,规则非常简单:每组有两个小时,从一个数据集中出发,基于 ClickHouse 打造一个有趣的可视化演示。我也参与了这次活动,下面是我完成的项目。

图片

数据集

本次使用的数据集是公开发布的,遵循 Apache 2.0 协议,支持免费下载和自由使用。数据集中包含各种地图上的地点信息,如商店、餐厅、公园、游乐场、纪念碑等,并附带分类、邮箱等元数据。以 ClickHouse 的处理能力来看,这个数据集规模并不大,记录数略高于 1 亿。但从类别来看,它可能是目前最大规模的此类开源数据集。

此前我曾为全球航班流量制作过一个可视化工具,实时处理超过 1300 亿条记录,因此用这个工具来处理 Foursquare 的这个“小数据集”几乎毫无压力。我决定直接复用它来进行可视化。

数据预览

想要快速预览并查询该数据集,可以直接使用 s3 表函数:

:) SELECT * FROM s3('s3://fsq-os-places-us-east-1/release/dt=2025-04-08/places/parquet/*') LIMIT 10

Row 1:
──────
fsq_place_id:        4ed7a0b89adf06cbf6d71fec
name:                Частная Бильярдная
latitude:            55.82704778252206
longitude:           37.44663365528853
address:             ᴺᵁᴸᴸ
locality:            ᴺᵁᴸᴸ
region:              ᴺᵁᴸᴸ
postcode:            ᴺᵁᴸᴸ
admin_region:        ᴺᵁᴸᴸ
post_town:           ᴺᵁᴸᴸ
po_box:              ᴺᵁᴸᴸ
country:             RU
date_created:        2011-12-01
date_refreshed:      2013-01-13
date_closed:         ᴺᵁᴸᴸ
tel:                 ᴺᵁᴸᴸ
website:             ᴺᵁᴸᴸ
email:               ᴺᵁᴸᴸ
facebook_id:         ᴺᵁᴸᴸ
instagram:           ᴺᵁᴸᴸ
twitter:             ᴺᵁᴸᴸ
fsq_category_ids:    ['4bf58dd8d48988d1e3931735']
fsq_category_labels: ['Arts and Entertainment > Pool Hall']
placemaker_url:      https://foursquare.com/placemakers/review-place/4ed7a0b89adf06cbf6d71fec
geom:                @B�+J�`�@K�ܳ��
bbox:                (37.44663365528853,55.82704778252206,37.44663365528853,55.82704778252206)

:) SELECT * FROM s3('s3://fsq-os-places-us-east-1/release/dt=2025-04-08/places/parquet/*')
   WHERE address IS NOT NULL AND postcode IS NOT NULL AND instagram IS NOT NULL LIMIT 10

Row 1:
──────
fsq_place_id:        643c2e2fc5a3b53de7ddfea7
name:                VIBE Nagymaros
latitude:            47.781879
longitude:           18.946042
address:             Szamaras Utca
locality:            Nagymaros
region:              PE
postcode:            2626
admin_region:        ᴺᵁᴸᴸ
post_town:           ᴺᵁᴸᴸ
po_box:              ᴺᵁᴸᴸ
country:             HU
date_created:        2023-04-16
date_refreshed:      2024-10-18
date_closed:         ᴺᵁᴸᴸ
tel:                 ᴺᵁᴸᴸ
website:             http://www.vibenagymaros.hu
email:               ᴺᵁᴸᴸ
facebook_id:         ᴺᵁᴸᴸ
instagram:           vibenagymaros
twitter:             ᴺᵁᴸᴸ
fsq_category_ids:    ['56aa371be4b08b9a8d5734e1']
fsq_category_labels: ['Travel and Transportation > Lodging > Vacation Rental']
placemaker_url:      https://foursquare.com/placemakers/review-place/643c2e2fc5a3b53de7ddfea7
geom:                @2�/���v@G��o6�
bbox:                (18.946042,47.781879,18.946042,47.781879)

通过 clickhouse-local 命令行工具,可以非常方便地分析本地或外部数据。这个小工具具备完整的 ClickHouse 引擎能力。

使用 DESCRIBE 语句可以自动推断数据的表结构(schema):

:) DESCRIBE s3('s3://fsq-os-places-us-east-1/release/dt=2025-04-08/places/parquet/*')

    ┌─name────────────────┬─type────────────────────────┐
 1. │ fsq_place_id        │ Nullable(String)            │
 2. │ name                │ Nullable(String)            │
 3. │ latitude            │ Nullable(Float64)           │
 4. │ longitude           │ Nullable(Float64)           │
 5. │ address             │ Nullable(String)            │
 6. │ locality            │ Nullable(String)            │
 7. │ region              │ Nullable(String)            │
 8. │ postcode            │ Nullable(String)            │
 9. │ admin_region        │ Nullable(String)            │
10. │ post_town           │ Nullable(String)            │
11. │ po_box              │ Nullable(String)            │
12. │ country             │ Nullable(String)            │
13. │ date_created        │ Nullable(String)            │
14. │ date_refreshed      │ Nullable(String)            │
15. │ date_closed         │ Nullable(String)            │
16. │ tel                 │ Nullable(String)            │
17. │ website             │ Nullable(String)            │
18. │ email               │ Nullable(String)            │
19. │ facebook_id         │ Nullable(Int64)             │
20. │ instagram           │ Nullable(String)            │
21. │ twitter             │ Nullable(String)            │
22. │ fsq_category_ids    │ Array(Nullable(String))     │
23. │ fsq_category_labels │ Array(Nullable(String))     │
24. │ placemaker_url      │ Nullable(String)            │
25. │ geom                │ Nullable(String)            │
26. │ bbox                │ Tuple(                     ↴│
    │                     │↳    xmin Nullable(Float64),↴│
    │                     │↳    ymin Nullable(Float64),↴│
    │                     │↳    xmax Nullable(Float64),↴│
    │                     │↳    ymax Nullable(Float64)) │
    └─────────────────────┴─────────────────────────────┘

 

导入 ClickHouse

我创建了如下表结构:

CREATE TABLE foursquare_mercator
(
    fsq_place_id String,
    name String,
    latitude Float64,
    longitude Float64,
    address String,
    locality String,
    region LowCardinality(String),
    postcode LowCardinality(String),
    admin_region LowCardinality(String),
    post_town LowCardinality(String),
    po_box LowCardinality(String),
    country LowCardinality(String),
    date_created Nullable(Date),
    date_refreshed Nullable(Date),
    date_closed Nullable(Date),
    tel String,
    website String,
    email String,
    facebook_id String,
    instagram String,
    twitter String,
    fsq_category_ids Array(String),
    fsq_category_labels Array(String),
    placemaker_url String,
    geom String,
    bbox Tuple(
        xmin Nullable(Float64),
        ymin Nullable(Float64),
        xmax Nullable(Float64),
        ymax Nullable(Float64)),

    category LowCardinality(String) ALIAS fsq_category_labels[1],
    mercator_x UInt32 MATERIALIZED 0xFFFFFFFF * ((longitude + 180) / 360),
    mercator_y UInt32 MATERIALIZED 0xFFFFFFFF * ((1 / 2) - ((log(tan(((latitude + 90) / 360) * pi())) / 2) / pi())),
    INDEX idx_x mercator_x TYPE minmax,
    INDEX idx_y mercator_y TYPE minmax
)
ORDER BY mortonEncode(mercator_x, mercator_y)

该表大部分字段与原始数据一致。同时,我新增了两个物化列 mercator_x 和 mercator_y,用于将地理坐标(经纬度)映射为 Web Mercator 投影坐标。在该投影方式下,坐标可用两个 UInt32 整数表示,从而便于将地图划分为图块(tile)。此外,我还按照空间填充曲线(space-filling curve)对表进行排序,并添加了两个 minmax 索引以加快检索速度。ClickHouse 本身就具备构建实时地图应用所需的全部能力。

随后,我通过以下查询将数据导入到了表中:

INSERT INTO foursquare_mercator SELECT * FROM s3('s3://fsq-os-places-us-east-1/release/dt=2025-04-08/places/parquet/*')

最后,我通过如下语句将数据导入,整个加载过程仅耗时 42 秒,最终占用存储约 11 GB。

可视化展示

我对 adsb.exposed 工具仅做了少量修改,整体只更改了 48 行代码,主要是替换了表名,并添加了一些新的查询示例。

打开页面的那一刻,我被最终呈现的效果惊艳到了——清晰、美观,令人印象深刻!

图片

图片

此外,图表还能展现许多有趣的数据洞察。例如,你可以框选东京地区,然后在全球范围内筛选清酒(Sake)酒吧;或者分析哪些国家的 ATM 机数量最多,哪些最少。

对比其他可视化工具

当然,这种类型的可视化并非首次出现,之前已有一些类似项目。

Foursquare Studio 的可视化在风格上与本项目接近,但加载速度较慢,图像分辨率也较低。其采用 H3 六边形网格进行聚合,而本项目直接按单像素维度进行可视化。在如阿尔卑斯山这样的复杂地形区域,细节差异尤其明显。

图片

图片

需要指出的是,本工具未对不同纬度区域的点密度进行标准化处理,而原始版本对此进行了补偿处理。此外,我的工具通过加载栅格化图块,在浏览器中几乎不产生卡顿;而 Foursquare Studio 的可视化会显著拖慢浏览器性能。

Simon Wilson 的项目使用 DuckDB 实现了一个基础版本的可视化,功能较为简单。

Mark 在其博客中展示的可视化结果也非常精美,与本项目风格相近,但其展示内容为静态图像,缺乏交互功能。

Kepler.gl 是一个支持 GPU 加速的浏览器端本地可视化工具。在我使用 MacBook Pro M3 进行测试时,它只能处理约一百万条记录,并且渲染过程中浏览器表现出明显的性能瓶颈。虽然 Kepler.gl 在视觉呈现方面极具特色,但在数据处理能力上存在限制。

结语

ClickHouse 非常适合用于大规模地理空间数据的分析。Foursquare 数据集规模约为 1 亿条记录,而 ADS-B 数据集已超过 1300 亿条,并持续增长中。ClickHouse 甚至支持客户处理规模达数十万亿记录的数据集。在应对此类高负载场景时,ClickHouse 不仅响应迅速,性能也极为稳定可靠。

征稿启示

面向社区长期正文,文章内容包括但不限于关于 ClickHouse 的技术研究、项目实践和创新做法等。建议行文风格干货输出&图文并茂。质量合格的文章将会发布在本公众号,优秀者也有机会推荐到 ClickHouse 官网。请将文章稿件的 WORD 版本发邮件至:Tracy.Wang@clickhouse.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值