메인 콘텐츠로 건너뛰기

데이터셋

Foursquare에서 제공하는 이 데이터셋은 다운로드할 수 있으며 Apache 2.0 라이선스에 따라 무료로 사용할 수 있습니다. 상점, 음식점, 공원, 놀이터, 기념물 등 상업 시설과 관련된 관심 지점(POI) 데이터 1억 건 이상이 포함되어 있습니다. 또한 이러한 장소의 카테고리, 소셜 미디어 정보 등의 추가 메타데이터도 포함되어 있습니다.

데이터 탐색

데이터를 탐색하기 위해 전체 ClickHouse engine을 제공하는 작은 명령줄 도구인 clickhouse-local을 사용하겠습니다. 물론 ClickHouse Cloud, clickhouse-client, 또는 chDB를 사용할 수도 있습니다. 데이터가 저장된 S3 버킷에서 데이터를 조회하려면 다음 쿼리를 실행하세요:
Query
SELECT * FROM s3('s3://fsq-os-places-us-east-1/release/dt=2025-04-08/places/parquet/*') LIMIT 1
Response
Row 1:
──────
fsq_place_id:        4e1ef76cae60cd553dec233f
name:                @VirginAmerica In-flight Via @Gogo
latitude:            37.62120111687914
longitude:           -122.39003793803701
address:             ᴺᵁᴸᴸ
locality:            ᴺᵁᴸᴸ
region:              ᴺᵁᴸᴸ
postcode:            ᴺᵁᴸᴸ
admin_region:        ᴺᵁᴸᴸ
post_town:           ᴺᵁᴸᴸ
po_box:              ᴺᵁᴸᴸ
country:             US
date_created:        2011-07-14
date_refreshed:      2018-07-05
date_closed:         2018-07-05
tel:                 ᴺᵁᴸᴸ
website:             ᴺᵁᴸᴸ
email:               ᴺᵁᴸᴸ
facebook_id:         ᴺᵁᴸᴸ
instagram:           ᴺᵁᴸᴸ
twitter:             ᴺᵁᴸᴸ
fsq_category_ids:    ['4bf58dd8d48988d1f7931735']
fsq_category_labels: ['Travel and Transportation > Transport Hub > Airport > Plane']
placemaker_url:      https://foursquare.com/placemakers/review-place/4e1ef76cae60cd553dec233f
geom:                �^��a�^@B�
bbox:                (-122.39003793803701,37.62120111687914,-122.39003793803701,37.62120111687914)
꽤 많은 필드가 ᴺᵁᴸᴸ이므로, 좀 더 유용한 데이터를 반환하도록 쿼리에 몇 가지 조건을 추가할 수 있습니다:
Query
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 1
Row 1:
──────
fsq_place_id:        59b2c754b54618784f259654
name:                Villa 722
latitude:            ᴺᵁᴸᴸ
longitude:           ᴺᵁᴸᴸ
address:             Gijzenveldstraat 75
locality:            Zutendaal
region:              Limburg
postcode:            3690
admin_region:        ᴺᵁᴸᴸ
post_town:           ᴺᵁᴸᴸ
po_box:              ᴺᵁᴸᴸ
country:             ᴺᵁᴸᴸ
date_created:        2017-09-08
date_refreshed:      2020-01-25
date_closed:         ᴺᵁᴸᴸ
tel:                 ᴺᵁᴸᴸ
website:             https://www.landal.be
email:               ᴺᵁᴸᴸ
facebook_id:         522698844570949 -- 5,227조
instagram:           landalmooizutendaal
twitter:             landalzdl
fsq_category_ids:    ['56aa371be4b08b9a8d5734e1']
fsq_category_labels: ['Travel and Transportation > Lodging > Vacation Rental']
placemaker_url:      https://foursquare.com/placemakers/review-place/59b2c754b54618784f259654
geom:                ᴺᵁᴸᴸ
bbox:                (NULL,NULL,NULL,NULL)
다음 쿼리를 실행하여 DESCRIBE로 데이터에서 자동 추론된 스키마를 확인하세요:
Query
DESCRIBE s3('s3://fsq-os-places-us-east-1/release/dt=2025-04-08/places/parquet/*')
Response
    ┌─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에 데이터 로드하기

데이터를 디스크에 영구 저장하려면 clickhouse-server 또는 ClickHouse Cloud를 사용할 수 있습니다. 테이블을 생성하려면 다음 명령을 실행하세요:
Query
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)
LowCardinality 데이터 타입이 여러 컬럼에 사용된 점에 주목하십시오. 이렇게 하면 데이터 타입의 내부 표현이 딕셔너리 인코딩 방식으로 바뀝니다. 딕셔너리 인코딩된 데이터로 작업하면 많은 애플리케이션에서 SELECT 쿼리 성능이 크게 향상됩니다. 또한, 지도를 타일로 더 쉽게 분할할 수 있도록 lat/lon 좌표를 Web Mercator projection에 매핑하는 두 개의 UInt32 MATERIALIZED 컬럼인 mercator_xmercator_y를 생성합니다:
mercator_x UInt32 MATERIALIZED 0xFFFFFFFF * ((longitude + 180) / 360),
mercator_y UInt32 MATERIALIZED 0xFFFFFFFF * ((1 / 2) - ((log(tan(((latitude + 90) / 360) * pi())) / 2) / pi())),
위에서 각 컬럼에서 어떤 일이 일어나는지 살펴보겠습니다. mercator_x 이 컬럼은 경도 값을 메르카토르 도법의 X 좌표로 변환합니다:
  • longitude + 180은 경도 범위를 [-180, 180]에서 [0, 360]으로 이동시킵니다
  • 360으로 나누면 값을 0과 1 사이로 정규화합니다
  • 0xFFFFFFFF(32비트 부호 없는 최대 정수의 16진수 표현)를 곱하면 이 정규화된 값을 32비트 정수의 전체 범위로 확장합니다
mercator_y 이 컬럼은 위도 값을 메르카토르 도법의 Y 좌표로 변환합니다:
  • latitude + 90은 위도 범위를 [-90, 90]에서 [0, 180]으로 이동시킵니다
  • 360으로 나눈 뒤 pi()를 곱하면 삼각 함수에 사용할 라디안으로 변환됩니다
  • log(tan(...)) 부분은 메르카토르 도법 공식의 핵심입니다
  • 0xFFFFFFFF를 곱하면 32비트 정수의 전체 범위로 확장합니다
MATERIALIZED를 지정하면 ClickHouse가 데이터를 INSERT할 때 이러한 컬럼의 값을 계산하므로, 원본 데이터 스키마에 포함되지 않은 이 컬럼들을 INSERT statement에서 별도로 지정할 필요가 없습니다. 테이블은 mortonEncode(mercator_x, mercator_y)를 기준으로 정렬되며, 이는 지리공간 쿼리 성능을 크게 향상시키기 위해 mercator_x, mercator_y에 대한 Z-order 공간 충전 곡선을 생성합니다. 이 Z-order 곡선 정렬은 데이터가 공간적 인접성을 기준으로 물리적으로 구성되도록 합니다:
ORDER BY mortonEncode(mercator_x, mercator_y)
검색 속도를 높이기 위해 minmax 인덱스 2개도 생성됩니다:
INDEX idx_x mercator_x TYPE minmax,
INDEX idx_y mercator_y TYPE minmax
보시는 것처럼 ClickHouse에는 실시간 매핑 애플리케이션에 필요한 모든 기능이 갖춰져 있습니다! 데이터를 불러오려면 다음 쿼리를 실행하세요:
INSERT INTO foursquare_mercator 
SELECT * FROM s3('s3://fsq-os-places-us-east-1/release/dt=2025-04-08/places/parquet/*')

데이터 시각화

이 데이터셋으로 무엇을 할 수 있는지 보려면 adsb.exposed를 확인해 보세요. adsb.exposed는 원래 공동 창립자이자 CTO인 Alexey Milovidov가 ADS-B(Automatic Dependent Surveillance-Broadcast) 비행 데이터를 시각화하기 위해 만들었으며, 이 데이터는 규모가 1000배 더 큽니다. 이후 사내 해커톤에서 Alexey가 이 도구에 Foursquare 데이터를 추가했습니다. 특히 인상적인 시각화 몇 가지를 아래에 소개합니다.
마지막 수정일 2026년 6월 10일