PostGIS 입문 — 지리 데이터 타입과 공간 쿼리

PostgreSQL 공간 데이터 확장 PostGIS의 geometry·geography 타입 차이, OGC 지오메트리 타입 계층(POINT·LINESTRING·POLYGON·MULTI*), SRID와 좌표계 개념, ST_DWithin·ST_Contains·ST_Intersects 등 핵심 공간 함수, GiST 인덱스 적용법을 설명합니다.

· 6 min read · PALDYN Team

지난 글에서 쿼리 통계 수집 도구를 살펴봤다. 이번에는 PostgreSQL의 가장 강력한 확장 중 하나인 PostGIS를 소개한다. PostGIS는 PostgreSQL에 지리 공간(Geospatial) 데이터 타입과 공간 함수를 추가해 지도·위치 기반 서비스 데이터를 SQL로 처리할 수 있게 한다.

PostGIS란

PostGIS는 OGC(Open Geospatial Consortium)의 Simple Features 표준을 구현한 확장이다. 좌표 데이터를 저장하고, “1km 내 매장 검색”, “폴리곤 안에 있는 POI 찾기”, “두 도로가 교차하는지 확인” 같은 공간 질의를 SQL로 처리한다.

-- 설치 (서버에 postgis 패키지 필요)
CREATE EXTENSION IF NOT EXISTS postgis;

-- 버전 확인
SELECT PostGIS_Version();

지오메트리 타입 계층

PostGIS 지오메트리 타입 계층

모든 타입은 geometry라는 최상위 타입의 서브타입이다.

타입설명예시
POINT단일 좌표매장 위치, GPS 로그
LINESTRING순서 있는 점의 연결도로, 경로
POLYGON닫힌 링(면)행정구역, 건물 외곽
MULTIPOINT점 집합분산된 POI 그룹
MULTILINESTRING선 집합끊긴 도로망
MULTIPOLYGON면 집합도서(섬) 행정구역
GEOMETRYCOLLECTION혼합 집합복합 지형

geometry vs geography

-- geometry: 평면(유클리드) 좌표계
-- 좌표 단위는 SRID가 결정 (도, 미터 등)
location geometry(Point, 4326)  -- WGS84 경위도

-- geography: 구면 좌표계 (WGS84 기준)
-- 거리 단위는 항상 미터
location geography(Point, 4326)

geography 타입은 지구 곡률을 고려해 거리를 계산한다. 서울-부산 거리는 geography로 계산해야 정확하다. 하지만 연산 비용이 geometry보다 높고, 지원하는 함수 수가 적다.

같은 도시 내 단거리 계산이라면 geometry에 투영 좌표계(EPSG:5181 한국 TM 등)를 쓰는 것이 빠르다.

SRID — 좌표 참조 시스템

SRID(Spatial Reference Identifier)는 좌표계를 식별하는 번호다.

SRID설명
4326WGS84 경위도 (GPS)
3857Web Mercator (구글 지도, OpenStreetMap)
5174한국 중부원점 TM (레거시)
5181한국 중부원점 TM (GRS80)
-- SRID 확인
SELECT srid, auth_name, srtext FROM spatial_ref_sys WHERE srid = 4326;

-- 좌표계 변환: 4326 → 5181
SELECT ST_Transform(geom, 5181) FROM pois;

서로 다른 SRID의 지오메트리를 직접 비교하면 오류가 발생한다. 반드시 같은 SRID로 변환한 후 연산해야 한다.

테이블 생성과 데이터 입력

-- 매장 테이블
CREATE TABLE stores (
  id      SERIAL PRIMARY KEY,
  name    TEXT,
  location geography(Point, 4326)
);

-- WKT(Well-Known Text)로 입력
INSERT INTO stores (name, location) VALUES
  ('강남점', ST_MakePoint(127.0276, 37.4979)),
  ('서초점', ST_GeogFromText('POINT(127.0325 37.4862)'));

-- WKB(Well-Known Binary) 또는 GeoJSON으로도 입력 가능
INSERT INTO stores (name, location)
VALUES ('판교점', ST_GeogFromWKB(E'\\x0101000020E6100000...'));

핵심 공간 함수

PostGIS 핵심 공간 함수

거리 관련

-- 두 점 사이의 거리 (미터)
SELECT ST_Distance(
  'POINT(127.0276 37.4979)'::geography,
  'POINT(127.0325 37.4862)'::geography
);

-- 반경 내 검색 (가장 자주 쓰는 패턴)
SELECT name, ST_Distance(location, ref) AS dist_m
FROM stores,
  ST_MakePoint(127.0276, 37.4979)::geography AS ref(ref)
WHERE ST_DWithin(location, ref, 1000)  -- 1000m 이내
ORDER BY dist_m;

ST_DWithin은 인덱스를 활용하므로 ST_Distance(...) < 1000 형식보다 훨씬 빠르다.

위상 관계

-- 포함 여부
SELECT ST_Contains(polygon_geom, point_geom);   -- 완전 포함
SELECT ST_Within(point_geom, polygon_geom);      -- 역방향
SELECT ST_Intersects(geom_a, geom_b);            -- 겹침 여부
SELECT ST_Touches(geom_a, geom_b);               -- 경계 접촉
SELECT ST_Crosses(line1, line2);                  -- 교차

-- 경계 버퍼 (500m 반경 폴리곤 생성)
SELECT ST_Buffer(location::geometry, 500) FROM stores WHERE id = 1;

출력 형식

SELECT ST_AsText(geom);       -- WKT: POINT(127.02 37.49)
SELECT ST_AsGeoJSON(geom);    -- GeoJSON 문자열
SELECT ST_AsKML(geom);        -- KML (Google Earth)
SELECT ST_AsSVG(geom);        -- SVG 경로
SELECT ST_X(point), ST_Y(point);  -- 경도, 위도 추출

GiST 인덱스

공간 쿼리 성능의 핵심은 GiST(Generalized Search Tree) 인덱스다. 공간 객체의 MBR(Minimum Bounding Rectangle)을 R-Tree 구조로 인덱싱한다.

-- 공간 인덱스 생성
CREATE INDEX idx_stores_location ON stores USING GIST (location);

-- 분석 후 최적화
ANALYZE stores;

ST_DWithin, ST_Contains, ST_Intersects 등 대부분의 공간 함수는 GiST 인덱스를 활용한다. 인덱스 없이 대용량 공간 쿼리를 실행하면 시퀀셜 스캔으로 매우 느려진다.

좌표 클러스터링

같은 지역의 데이터를 물리적으로 가깝게 배치하면 공간 쿼리 성능이 향상된다.

-- GiST 인덱스 순서로 테이블 재구성
CLUSTER stores USING idx_stores_location;

실용 예: 가장 가까운 N개 매장

-- KNN (K-Nearest Neighbor) 쿼리
SELECT name,
       location <-> 'POINT(127.0276 37.4979)'::geography AS dist
FROM stores
ORDER BY location <-> 'POINT(127.0276 37.4979)'::geography
LIMIT 5;

<->는 PostGIS의 거리 연산자로, GiST 인덱스를 이용한 KNN 검색을 수행한다. ORDER BY dist LIMIT 5보다 훨씬 효율적이다.

PostGIS는 복잡한 공간 데이터 처리를 전담 GIS 서버 없이 PostgreSQL 안에서 처리할 수 있게 해준다. 위치 기반 서비스, 물류 최적화, 도시 데이터 분석에 폭넓게 활용된다.


지난 글: pg_stat_statements — 쿼리 통계로 슬로우 쿼리 잡기

다음 글: PostgreSQL 전문 검색 — tsvector와 tsquery


읽어주셔서 감사합니다. 😊