공간정보 API 오류는 HTTP 요청보다 좌표의 의미를 잘못 이해해서 생기는 경우가 많다. 위도와 경도를 뒤집거나, WGS 84 좌표를 투영 좌표처럼 계산하거나, 직선 거리를 도로 거리로 표시하면 응답이 정상이어도 결과는 틀린다.

이 글은 이 저장소의 quartz/components/tools/GisCoordinateCalculator.tsx를 2026년 7월 23일 기준으로 확인한 결과에서 시작한다.

저장소 GIS 계산기에서 확인한 구현

현재 계산기는 외부 API를 호출하지 않고 브라우저에서 세 가지를 계산한다.

기능구현 기준입력 예
두 점 직선 거리평균 지구 반지름 6,371,008.8m의 Haversine 식서울시청과 강남역 위경도
DD → DMS십진 도를 도·분·초로 변환37.5665, 126.9780
위경도 → UTMWGS 84 타원체 상수로 zone과 easting·northing 계산서울은 zone 52

프리셋 좌표와 같은 수식을 Node.js로 다시 실행한 결과는 다음과 같았다.

{
  "meters": 8793,
  "kilometers": "8.79",
  "walkMinutes": 132,
  "driveMinutes": 13
}

여기서 검증된 것은 두 점의 구면 직선 거리가 약 8.79km라는 계산뿐이다. 도보 132분과 차량 13분은 각각 시속 4km, 40km로 단순 나눈 값이다. 실제 경로, 신호, 고도, 교통량을 반영하지 않는다.

1. 좌표계와 축 순서를 응답 계약에 적는다

최소한 다음 필드를 API 계약과 저장 스키마에 함께 기록한다.

{
  "crs": "EPSG:4326",
  "axis_order_in_this_api": "longitude, latitude",
  "longitude": 126.9780,
  "latitude": 37.5665,
  "source": "...",
  "observed_at": "..."
}

GeoJSON은 위치 배열의 첫 두 요소를 longitude, latitude 순서로 정의한다. 원문은 IETF RFC 7946에서 확인할 수 있다. 반면 일부 좌표계 정의와 기관 API는 축 순서 표현이 다를 수 있으므로 문서의 실제 예제를 확인해야 한다.

입력 검증도 필요하다.

  • 위도: -90 이상 90 이하
  • 경도: -180 이상 180 이하
  • 숫자가 아니면 0으로 대체하지 말고 오류 반환
  • CRS가 다르면 명시적으로 변환
  • 단위를 m, km, degree 중 하나로 고정

현재 GIS 계산기는 parseFloat 결과가 유효하지 않을 때 0으로 바꾸며 범위 검사가 없다. 공개 API 입력 검증 코드로 그대로 재사용하면 안 된다.

2. API 키를 사용 위치에 맞게 제한한다

서버용 비밀 키를 브라우저 JavaScript에 넣으면 사용자가 키를 볼 수 있다. 공급자가 브라우저 공개 키 사용을 허용한다면 referrer와 API 종류를 제한하고, 서버용 키는 서버의 비밀 저장소에 둔다.

Google Maps Platform 보안 지침은 애플리케이션 제한과 API 제한을 함께 적용하고, 앱별 키를 분리하며, 서버용 키를 소스 트리에 저장하지 말라고 안내한다.

저장소에 올리기 전 확인할 명령 예시는 다음과 같다.

git grep -n -I -E "(api[_-]?key|secret|token)[[:space:]]*[:=]"

이 명령은 후보 문자열을 찾을 뿐 비밀 탐지 도구를 대체하지 않는다. 테스트 키나 문서 예시도 나오므로 결과를 사람이 확인한다.

3. 호출 제한과 재시도 계약을 문서에서 확인한다

공공 API가 모두 HTTP 429와 Retry-After를 같은 방식으로 주는 것은 아니다. 오류 코드가 HTTP 200 안의 XML 필드로 오는 서비스도 있다.

연동 전에 다음 표를 채운다.

항목문서에서 찾을 값
일일·분당 쿼터키, 사용자, IP 중 어느 단위인지
초과 응답HTTP 상태와 본문 오류 코드
재시도 시점Retry-After 또는 공급자 지침
페이지 크기최대 rows와 전체 건수 필드
타임아웃연결·응답 각각의 상한
변경 공지버전·폐기 공지를 받는 경로

공공데이터포털의 인증키와 호출 URL 구성은 공식 Gateway Swagger 안내서 같은 제공 기관 자료를 기준으로 확인한다. 개별 데이터셋의 상세 조건이 우선이다.

4. 정상 응답과 좋은 데이터는 다르다

공간 데이터는 샘플을 고정해 회귀 테스트를 만든다.

test_cases = [
  {"name": "서울시청", "lat": 37.5665, "lon": 126.9780},
  {"name": "날짜변경선 동쪽", "lat": 0.0, "lon": 179.9999},
  {"name": "날짜변경선 서쪽", "lat": 0.0, "lon": -179.9999},
  {"name": "적도·본초자오선", "lat": 0.0, "lon": 0.0}
]

검사 항목은 다음과 같다.

  • 필수 geometry가 비어 있지 않은가?
  • 좌표가 선언한 CRS와 범위에 맞는가?
  • 날짜변경선과 극지방에서 계산이 깨지지 않는가?
  • 업데이트 날짜와 기준 시점이 있는가?
  • 같은 객체의 ID가 버전 사이에서 안정적인가?
  • 샘플을 지도에 그렸을 때 예상 지역에 나타나는가?

5. 캐시와 재배포 권한을 함께 확인한다

기술적으로 캐시할 수 있어도 약관이 영구 저장이나 2차 제공을 제한할 수 있다. 다음 내용을 데이터셋별로 기록한다.

질문기록할 근거
상업적 이용 가능한가?제공 페이지의 이용 조건
출처 문구는 무엇인가?기관이 요구한 정확한 표기
원본을 재배포할 수 있는가?라이선스와 약관 조항
저장 가능한 기간은?캐시·보관 조건
개인정보가 포함되는가?필드 정의와 결합 위험 검토
삭제·정정 요청 경로는?제공 기관 연락처

“공공”이라는 이유만으로 모든 용도를 허용한다고 가정하지 않는다. 법률 판단이 필요한 경우 제공 기관 또는 전문가 확인을 받는다.

API 도입 전 최소 회귀 테스트

외부 응답을 바로 내부 모델로 쓰지 말고 어댑터에서 검증한다.

def normalize_point(item):
    lon = float(item["longitude"])
    lat = float(item["latitude"])
    if not -180 <= lon <= 180:
        raise ValueError("longitude out of range")
    if not -90 <= lat <= 90:
        raise ValueError("latitude out of range")
    return {
        "type": "Point",
        "coordinates": [lon, lat],
    }

응답 원문 fixture를 버전 관리하면 공급자 필드 변경을 CI에서 찾을 수 있다. 다만 fixture에 실제 주소나 개인 위치가 들어가면 익명화가 먼저다.

한계

  • 이 저장소의 GIS 계산기는 외부 공간정보 API를 호출하지 않는다. API 성공률, 쿼터, 응답 시간은 측정하지 않았다.
  • 8.79km는 Haversine 직선 거리이며 도로·도보 경로가 아니다.
  • 자체 UTM 변환은 전문 좌표 라이브러리와 전체 EPSG 예외 규칙을 대체하지 못한다.
  • 현재 UI는 남반구에서도 zone 표시 문자열에 N을 붙일 가능성이 있어 전 세계 좌표 사용 전 별도 검증이 필요하다.
  • 공급자별 약관과 쿼터는 바뀔 수 있으므로 이 글보다 해당 데이터셋의 공식 문서가 우선한다.
  • 개인정보·라이선스 항목은 일반 개발 체크리스트이며 법률 자문이 아니다.

공간정보 API 연동의 첫 성공 기준은 화면에 점이 보이는 것이 아니다. 좌표의 의미, 출처, 변경 시점과 사용 권한을 같은 요청 ID로 추적할 수 있어야 한다.

📦추천 상품🔒 개인정보 수집 없음

쿠팡 로켓배송 특가 & 가성비 추천 제품 확인

이 글에서 소개하거나 추천하는 IT 기기, 가공식품, 가성비 필수 꿀템을 쿠팡 로켓배송 최저가로 빠르게 만나보세요.

쿠팡 최저가 확인하기 →