RSS

지도를 육각형으로 나누면 무엇이 달라질까: H3로 영업 지역 관리하기

지도 서비스를 만들다 보면 좌표를 저장하는 것 자체는 어렵지 않습니다. 업체 위치는 위도와 경도로 저장하면 되고, 지도 위에는 마커를 찍으면 됩니다.

문제는 그다음입니다.

  • 이 지역을 이미 검색했는가?
  • 어떤 키워드로 검색했는가?
  • 주변 지역 중 아직 조사하지 않은 곳은 어디인가?
  • 어느 지역에서 리드가 많이 발견됐는가?
  • 지도 확대 수준이 바뀌면 데이터를 어떻게 묶어야 하는가?

좌표만 저장해서는 이런 질문에 답하기 어렵습니다.

예를 들어 부천시에서 인쇄업을 검색해 업체 20개를 저장했다고 해보겠습니다. 며칠 뒤 다시 지도를 열었을 때 시스템은 사용자가 어느 범위까지 조사했는지 기억해야 합니다. 행정동 단위로 저장하면 경계가 너무 크고, 사용자가 직접 그린 사각형으로 저장하면 검색 영역이 계속 겹칩니다.

이 문제를 해결하는 도구가 H3입니다.

H3는 지구 표면을 일정한 육각형 셀로 나누고, 각 셀에 고유한 ID를 부여합니다. 위도와 경도를 H3에 넣으면 해당 위치가 어느 육각형에 속하는지 즉시 계산할 수 있습니다.

이 글에서는 H3가 무엇인지보다 왜 지도 기반 영업 시스템에 H3가 필요한지를 살펴봅니다.


역사: 차량 이동을 분석하려다 지구를 육각형으로 나눴다

H3는 Uber가 만든 오픈소스 지리 공간 인덱싱 시스템입니다.

Uber는 전 세계 도시에서 차량과 승객의 움직임을 다뤄야 했습니다. 특정 좌표 하나를 찾는 것보다 다음과 같은 질문이 중요했습니다.

  • 어느 지역에 승객 호출이 몰리는가?
  • 어느 구역에 차량이 부족한가?
  • 인접 지역으로 차량이 얼마나 이동하는가?
  • 도시별 데이터를 같은 기준으로 비교할 수 있는가?

행정구역은 이런 분석에 적합하지 않습니다. 행정구역은 국가와 도시마다 크기와 모양이 다르고, 사업 목적에 맞춰 만들어진 공간 단위도 아닙니다.

단순한 위도·경도 사각 격자 역시 문제가 있습니다. 지구는 구형에 가까운데 위도와 경도를 동일한 간격으로 나누면, 위치에 따라 격자의 실제 면적이 달라집니다. 극지방으로 갈수록 왜곡도 커집니다.

Uber는 이를 해결하기 위해 지구를 육각형 중심의 계층형 격자로 나누는 H3를 개발했습니다. H3의 첫 공개 버전은 2018년에 나왔으며, 현재는 Apache 2.0 라이선스로 공개돼 있습니다.

H3가 특별한 이유는 단순히 지도 위에 육각형을 그려주기 때문이 아닙니다.

각 육각형이 데이터베이스에서 사용할 수 있는 고유한 공간 ID가 되기 때문입니다.


구조: 좌표를 지역 ID로 바꾼다

일반적인 위치 데이터는 다음처럼 저장됩니다.

{
  "latitude": 37.4823,
  "longitude": 126.7931
}

이 좌표만 보고는 해당 위치가 어떤 검색 영역에 속하는지 바로 판단하기 어렵습니다.

H3에서는 좌표를 다음과 같은 셀 ID로 변환합니다.

8930e1d8c23ffff

이 문자열은 단순한 임의 값이 아닙니다.

  • 어느 기본 셀에서 출발했는지
  • 어느 해상도인지
  • 상위 셀의 어떤 하위 셀인지

등의 계층 정보가 하나의 64비트 인덱스 안에 들어 있습니다. H3는 해상도 0부터 15까지 지원하며, 해상도가 올라갈수록 셀이 작아집니다.

JavaScript에서는 다음처럼 좌표를 H3 셀로 변환할 수 있습니다.

import { latLngToCell } from "h3-js";

const cellId = latLngToCell(
  37.4823,
  126.7931,
  9
);

console.log(cellId);

여기서 마지막 숫자 9가 해상도입니다.

이제 시스템은 좌표 자체가 아니라 다음처럼 데이터를 관리할 수 있습니다.

{
  "h3Index": "8930e1d8c23ffff",
  "status": "PARTIAL",
  "searchedKeywords": [
    "인쇄업",
    "포장업"
  ],
  "leadCount": 12
}

즉 H3는 위치를 단순한 숫자 두 개에서 조회·집계·비교가 가능한 지역 단위로 바꿉니다.


왜 육각형인가

지도를 격자로 나누려면 사각형이 더 익숙해 보입니다.

실제로 간단한 시스템은 위도와 경도를 일정 간격으로 잘라 사각 격자를 만들 수 있습니다.

const GRID_SIZE = 0.005;

const gridX = Math.floor(longitude / GRID_SIZE);
const gridY = Math.floor(latitude / GRID_SIZE);

const gridId = `${gridX}:${gridY}`;

이 방식은 구현이 단순하지만, 주변 지역을 계산할 때 문제가 생깁니다.

사각형은 변으로 연결된 이웃 네 개와 대각선으로 연결된 이웃 네 개의 거리가 다릅니다. 같은 “한 칸 옆”이어도 상하좌우와 대각선의 실제 거리가 다릅니다.

육각형은 중심에서 인접한 여섯 셀까지의 관계가 비교적 균일합니다.

        ⬡ ⬡
      ⬡ ⬢ ⬡
        ⬡ ⬡

이 특징은 다음과 같은 분석에 유리합니다.

  • 주변 상권 탐색
  • 배달 가능 지역 계산
  • 차량 배치
  • 인구 밀도 분석
  • 기지국 커버리지
  • 영업 담당 구역 설정
  • 인접 미검색 지역 탐색

H3는 gridDisk 함수를 통해 특정 셀에서 일정 거리 안에 있는 주변 셀을 찾을 수 있습니다.

import { gridDisk } from "h3-js";

const nearbyCells = gridDisk(
  "8930e1d8c23ffff",
  1
);

이 셀들을 DB와 비교하면 바로 다음 질문에 답할 수 있습니다.

현재 지역 주변에서 아직 검색하지 않은 셀은 어디인가?


해상도: 몇 번을 써야 하는가

H3를 처음 도입할 때 가장 먼저 부딪히는 문제가 해상도입니다.

해상도가 낮으면 셀이 크고, 높아질수록 셀이 작아집니다.

해상도 활용 예시
6~7 도시·생활권 단위 통계
8 동네·넓은 상권
9 도보권·상권 블록
10 건물군·세부 블록

지도 기반 리드 발굴 시스템이라면 해상도 9가 비교적 적당합니다.

해상도 8은 한 셀 안에 업체가 너무 많이 포함될 수 있고, 해상도 10은 검색 단위가 지나치게 잘게 나뉠 수 있습니다.

중요한 것은 처음부터 완벽한 해상도를 정하는 것이 아닙니다. H3는 계층 구조이므로 세부 셀을 상위 셀로 묶어 집계할 수 있습니다.


전략: 지도 기반 리드 수집에서 H3가 필요한 이유

네이버 지도에서 특정 지역의 업체를 검색하는 기능을 만든다고 가정해보겠습니다.

사용자는 지도를 부천시 옥길동으로 이동한 뒤 인쇄업을 검색합니다.

시스템은 검색 결과에서 다음 정보를 가져옵니다.

업체명
업종
주소
좌표
홈페이지

이 결과를 리드 DB에 저장하는 것까지는 어렵지 않습니다.

문제는 검색 작업을 반복할 때 발생합니다.

지난번에 이 근처를 검색했나?
인쇄업만 검색했나, 제조업도 검색했나?
지도에서 어느 범위까지 조사했나?
같은 지역을 다시 검색하고 있지는 않나?

H3를 사용하면 검색할 때마다 현재 지도 영역을 셀 단위로 기록할 수 있습니다.

{
  "h3Index": "8930e1d8c23ffff",
  "keyword": "인쇄업",
  "searchedAt": "2026-08-05T14:00:00Z",
  "resultCount": 5,
  "savedLeadCount": 3
}

같은 셀에서 포장업을 검색하면 별도 이력을 추가합니다.

{
  "h3Index": "8930e1d8c23ffff",
  "keyword": "포장업",
  "searchedAt": "2026-08-05T14:20:00Z",
  "resultCount": 4,
  "savedLeadCount": 2
}

이제 셀별 검색 진행 상태를 만들 수 있습니다.

회색: 검색하지 않은 지역
노란색: 일부 키워드만 검색한 지역
초록색: 목표 키워드를 모두 검색한 지역
빨간색: 리드가 많이 발견된 지역

이것은 단순히 지도를 예쁘게 칠하는 기능이 아닙니다.

영업 활동을 공간 데이터로 바꾸는 작업입니다.


구현: H3를 영업 시스템에 붙이는 방법

전체 흐름은 다음과 같습니다.

사용자가 지도에서 위치 선택

중심 좌표 또는 지도 영역 확인

좌표를 H3 셀 ID로 변환

지역명 + 업종 키워드로 업체 검색

결과 업체의 좌표를 H3 셀로 변환

해당 셀의 리드 및 검색 이력 저장

지도에 셀별 상태 표시

1. 좌표를 H3 셀로 변환

import { latLngToCell } from "h3-js";

const resolution = 9;

const h3Index = latLngToCell(
  latitude,
  longitude,
  resolution
);

2. 셀 경계를 지도에 표시

import { cellToBoundary } from "h3-js";

const boundary = cellToBoundary(h3Index);

반환된 좌표를 네이버 지도의 Polygon으로 그립니다.

const polygon = new naver.maps.Polygon({
  map,
  paths: boundary.map(([lat, lng]) =>
    new naver.maps.LatLng(lat, lng)
  ),
  fillOpacity: 0.25,
  strokeWeight: 1
});

3. 검색 이력 저장

type MapSearchHistory = {
  id: string;
  h3Index: string;
  resolution: number;
  keyword: string;
  resultCount: number;
  savedLeadCount: number;
  searchedAt: Date;
};

4. 셀 상태 집계

type MapSearchCell = {
  h3Index: string;
  status: "UNSEARCHED" | "PARTIAL" | "COMPLETED";
  searchedKeywords: string[];
  leadCount: number;
  lastSearchedAt: Date | null;
};

지도 전체 영역을 셀로 채우기

현재 지도 화면 전체를 조사 대상으로 표시하려면 지도 영역을 여러 개의 H3 셀로 채워야 합니다.

H3의 polygonToCells를 사용하면 지정한 다각형 내부를 선택한 해상도의 셀로 변환할 수 있습니다.

import { polygonToCells } from "h3-js";

const cells = polygonToCells(
  [
    [south, west],
    [south, east],
    [north, east],
    [north, west],
    [south, west]
  ],
  9
);

이 기능을 사용하면 현재 지도 화면에 포함된 셀 목록을 구할 수 있습니다.

현재 지도에 보이는 셀: 42개
검색 완료: 18개
일부 검색: 7개
미검색: 17개

사용자가 현재 화면에서 미검색 지역만 검색 버튼을 누르면, 아직 이력이 없는 셀을 우선 대상으로 삼을 수 있습니다.


데이터 구조: 리드와 검색 지역을 분리해야 한다

리드와 검색 지역은 같은 데이터가 아닙니다.

업체는 하나의 좌표에 존재하지만, 검색 행위는 특정 셀과 키워드의 조합입니다.

리드

type Lead = {
  id: string;
  companyName: string;
  category: string | null;
  roadAddress: string | null;
  latitude: number;
  longitude: number;
  h3Index: string;
  phone: string | null;
  websiteUrl: string | null;
  source: "NAVER_LOCAL_SEARCH";
  stage: "NEW" | "CONTACTED" | "CONSENTED" | "CLOSED";
};

검색 이력

type SearchHistory = {
  id: string;
  h3Index: string;
  keyword: string;
  query: string;
  resultCount: number;
  createdLeadCount: number;
  searchedAt: Date;
};

검색 대상 키워드

type SearchKeyword = {
  id: string;
  name: string;
  category: string;
  isActive: boolean;
};

이 구조를 사용하면 셀별 검색 진행률을 계산할 수 있습니다.


중복 제거: H3는 보조키이지 업체 식별자가 아니다

같은 H3 셀 안에 있는 업체라고 해서 같은 업체는 아닙니다.

H3는 지역을 식별할 뿐, 회사를 식별하지 않습니다.

리드 중복 여부는 별도의 기준으로 판단해야 합니다.

1순위: 사업자등록번호
2순위: 전화번호
3순위: 업체명 + 도로명 주소
4순위: 업체명 + 좌표 근접도

H3는 중복 후보를 빠르게 줄이는 데 사용할 수 있습니다.

const candidateCells = gridDisk(
  newLead.h3Index,
  1
);
SELECT *
FROM leads
WHERE h3_index IN (...candidateCells);

즉 H3는 중복 제거의 최종 판단 기준이 아니라 공간적으로 가까운 후보를 빠르게 찾는 인덱스입니다.


한계: 육각형이라고 모든 공간 문제가 해결되지는 않는다

H3는 강력하지만 정확한 행정구역이나 토지 경계를 대체하지는 않습니다.

첫 번째 한계는 경계의 불일치입니다.

하나의 H3 셀
→ 두 개의 행정동에 걸칠 수 있음

하나의 산업단지
→ 여러 H3 셀로 나뉠 수 있음

정확한 경계 판정이 필요하다면 H3로 후보를 좁힌 뒤 별도의 point-in-polygon 검사를 해야 합니다.

두 번째는 셀 면적이 완전히 동일하지 않다는 점입니다.

H3는 지구를 정이십면체에 투영한 뒤 격자를 구성합니다. 지구 전체를 육각형만으로 빈틈없이 감쌀 수는 없기 때문에 각 해상도에는 12개의 오각형 셀이 존재합니다.

세 번째는 검색 API의 한계입니다.

H3로 지역을 정교하게 나눠도 외부 검색 API가 해당 셀 안의 모든 업체를 반환한다는 보장은 없습니다. H3는 검색 결과를 정리하고 검색 이력을 관리하는 도구이지, 외부 검색 API의 누락을 해결하는 도구는 아닙니다.

네 번째는 지나치게 작은 해상도입니다.

해상도를 너무 높이면 셀 수가 급격히 증가하고, DB 행과 지도 Polygon 수가 늘어나 렌더링 비용도 커집니다.


확장: 검색 이력을 넘어 영업 전략으로

H3를 도입하면 단순히 검색 완료 지역을 표시하는 것에서 끝나지 않습니다.

리드 밀도

셀마다 발견된 업체 수를 집계하면 업종별 밀도를 볼 수 있습니다.

금속가공 업체 밀집 지역
인쇄업체 밀집 지역
물류업체 밀집 지역

영업 성과

발견 리드 수
통화 연결 수
자료 발송 동의 수
견적 요청 수
계약 수

이를 H3 셀별로 집계하면 어느 지역의 영업 효율이 높은지 분석할 수 있습니다.

담당자 구역 배정

직원이 늘어나면 H3 셀을 묶어 담당 구역을 만들 수 있습니다.

다음 검색 지역 추천

현재까지의 검색 결과를 기반으로 주변 미검색 셀, 목표 업종 밀도, 기존 영업 성공률을 조합해 다음 조사 지역을 추천할 수 있습니다.

이 단계부터 지도는 단순한 검색 화면이 아니라 영업 의사결정 시스템이 됩니다.


H3가 해결하는 것은 지도가 아니라 기억이다

처음 H3를 보면 육각형 지도를 그리는 시각화 라이브러리처럼 보입니다.

하지만 H3의 핵심은 육각형 모양이 아닙니다.

좌표
→ 셀 ID

셀 ID
→ 검색 이력

검색 이력
→ 진행 상태

진행 상태
→ 영업 전략

지도는 계속 움직입니다.

사용자는 확대하고 축소하며, 검색 범위를 바꾸고, 여러 키워드를 반복해서 입력합니다. 좌표와 마커만 저장하면 시스템은 사용자의 작업 과정을 기억하지 못합니다.

H3를 사용하면 지도의 모든 위치가 고정된 ID를 갖게 됩니다.

이 셀은 검색했다.
이 셀에서는 인쇄업만 검색했다.
이 셀에서 리드가 12개 발견됐다.
이 셀은 지난달보다 계약 전환율이 높았다.

이 정보들이 쌓이면 지도는 단순한 배경이 아니라 데이터베이스의 인덱스가 됩니다.


마무리하며

지도 기반 업체 검색 기능은 네이버 지도를 띄우고 마커를 표시하는 것만으로도 만들 수 있습니다.

하지만 한 번 검색하고 끝나는 도구가 아니라, 수개월 동안 지역을 조사하고 리드를 축적하는 시스템을 만들려면 별도의 공간 관리 단위가 필요합니다.

행정구역은 너무 크고 불규칙합니다.

직접 만든 사각 격자는 단순하지만 주변 탐색과 계층 집계에 한계가 있습니다.

H3는 지구를 일정한 규칙의 셀로 나누고, 각 위치를 고유한 지역 ID로 변환합니다. 검색한 지역, 남은 지역, 리드가 많은 지역, 성과가 좋은 지역을 같은 기준으로 관리할 수 있습니다.

H3를 사용하는 진짜 이유는 육각형이 예뻐서가 아닙니다.

현실 세계의 위치를 데이터베이스가 이해할 수 있는 단위로 바꾸기 위해서입니다.

지도 위에 리드를 저장하는 단계에서는 위도와 경도만으로 충분합니다.

하지만 지도 위에서 영업 전략을 세우려면, 좌표를 지역으로 묶는 체계가 필요합니다.

그 역할을 하는 것이 H3입니다.