Abdullah Atif 작성. Geoapify Open Geospatial Program을 위해 제작된 배치 #2: AI × 지리공간(Geospatial).
방글라데시에서 응급 상황 발생? 먼저 999에 전화하세요. 이 프로젝트는 시설을 찾는 데 도움을 주지만, 응급 서비스 자체를 대체하지는 않습니다.
다카에서는 지도상으로 가장 가까워 보이는 병원이 실제로 가장 빨리 도착할 수 있는 곳이 아닐 때가 많습니다. 일방통행 도로, 운하, 강, 그리고 혼잡한 교통 상황은 그 답을 바꿉니다. 저는 AI 도구가 사람들이 응급 상황에서 실제로 던지는 질문, 즉 “어디로 가는 것이 가장 빠를까요?”에 답할 수 있는지, 그리고 어디서 오류가 발생하는지 확인하고 싶었습니다.
저는 Geoapify를 사용하여 이 동일한 질문을 세 가지 방식으로 답변했습니다:
- AI 에이전트: 런타임(run time)에서 Geoapify MCP 도구를 호출하는 방식 (Claude Code와 VS Code + GitHub Copilot에서 테스트).
- 소형 Python CLI: AI 코딩 에이전트가 Geoapify OpenAPI 사양으로부터 작성한 방식.
- 개발자가 아닌 사람을 위한 전화 친화적 웹 앱: “내 위치 사용” 버튼과 999에 전화를 걸 수 있는 기능, 방글라어 또는 영어 지원.
그 후 저는 이 파이프라인을 영어와 방글라어로 된 다카의 실제 장소 10곳에 테스트했습니다.
Architecture: three front ends, one four-step Geoapify pipeline
앱 사용해 보기: https://abdullah-atif.github.io/dhaka-emergency-finder/app/ ·
코드: https://github.com/Abdullah-Atif/dhaka-emergency-finder (MIT)
필요한 것들
- Python 3.9 이상 버전 (CLI는 표준 라이브러리만 사용합니다)
- https://myprojects.geoapify.com에서 받은 무료 Geoapify API 키
- 선택 사항: MCP를 지원하는 AI 클라이언트 (Claude Code, Claude Desktop, VS Code + GitHub Copilot, Cursor)
키를 환경 변수에 설정하세요. 절대 커밋하지 마세요:
export GEOAPIFY_API_KEY=your_key # macOS / Linux / Git Bash
$env:GEOAPIFY_API_KEY = "your_key" # Windows PowerShell
파트 1: 질문을 단계별로 (The question, step by step)
어떤 방법을 선택하든 답은 동일한 네 단계에서 나옵니다:
| 단계 | Geoapify MCP 도구 | Geoapify REST API |
|---|
| 1. 어디에 있나요? | geocode_address | 순방향 지오코딩 (Forward Geocoding) |
| ... | | |
3단계가 중요합니다. 직선 거리(straight-line distance)로 정렬하는 대신, 단일 요청으로 하나의 출발지에서 최대 10개의 후보지에 대한 이동 시간을 문의하고 시간 순서로 정렬합니다. traffic=approximated를 사용하세요: 이는 혼잡할 가능성이 있는 도로의 속도를 낮춥니다.
파트 2: AI 에이전트 (Geoapify MCP)
클라이언트 연결하기 (Connect the client)
Geoapify MCP는 https://api.geoapify.com/v1/mcp에 있는 원격 스트림 가능한 HTTP 서버(remote Streamable HTTP server)입니다. 이는 x-api-key 헤더에서 API 키를 통해 인증합니다. Claude Code에서는:
claude mcp add --transport http geoapify https://api.geoapify.com/v1/mcp --header "x-api-key: YOUR_API_KEY"
연결 상태를 확인하세요:
$ claude mcp list
geoapify: https://api.geoapify.com/v1/mcp (HTTP) - ✔ Connected
저는 이 명령을 Claude Code 2.1로 테스트했으며, VS Code + GitHub Copilot (아래 세션 3)으로도 테스트했습니다. 두 가지 모두의 설정은 agent/MCP_SETUP.md에 있습니다.
에이전트의 행동 지침 알려주기 (Tell the agent how to behave)
도구만으로는 충분하지 않습니다. 특히 비상 상황 사용 사례(emergency use case)의 경우, 에이전트에게 규칙이 필요합니다. 제가 작성한 agent/AGENT_INSTRUCTIONS.md에는 다음과 같이 명시되어 있습니다:
- 항상 "Call 999"로 시작할 것.
- 시설, 전화번호 또는 시간을 절대 지어내지 말 것. 모든 사실은 도구 결과에서 나와야 합니다.
- 거리가 아닌 **이동 시간(travel time)**으로 순위를 매길 것.
- 지오코딩(geocoding)이 도시만 일치시킨 경우, 시 중심지에 대한 답변 대신 랜드마크를 요청할 것.
세션 1: 영어 사용, 모든 호출 기록
저는 다음과 같이 질문했습니다: "Mirpur 10 golchottor에 있습니다. 차로 가장 빨리 갈 수 있는 병원은 어디인가요?" 이 첫 번째 세션을 위해,
에이전트(Claude Code)는 작은 JSON-RPC 헬퍼를 통해 MCP 도구를 호출했으며, 모든 요청과 응답을 기록할 수 있도록 했습니다.
에이전트가 직접 모든 호출을 결정했고, 헬퍼는 단지 이를 전송하는 역할만 했습니다. 총 6개의 도구 호출이 이루어졌습니다. 전체 기록은 저장소에서 확인할 수 있습니다.
발생한 내용은 다음과 같습니다:
geocode_address는 match_type: inner_part와 신뢰도(confidence) 1로 Mirpur 10 버스 터미널을 찾았습니다.
search_places는 75m에서 201m 이내에 병원으로 태그된 10개 장소를 찾았습니다.
calculate_route_matrix는 120초에서 429초의 이동 시간을 반환했습니다.
- 첫 번째
calculate_route 호출은 실패했습니다: geometry_scope "steps"는 include_instructions가 true여야 합니다. 오류는 명확했고, 에이전트는 한 번의 재시도로 이를 수정했습니다.
- 두 번째 호출은 작동했지만, 매트릭스에서 120초라고 한 반면 33초를 반환했습니다. 에이전트는 라우트 호출에
traffic=approximated 설정을 누락한 상태였습니다.
- 동일한
traffic 설정을 사용했을 때 경로는 121초가 걸렸으며, 이는 매트릭스 결과와 일치합니다.
제공된 답변:
응급 상황? 999에 전화하세요. 차로 가장 빠른 곳: AALOK HEALTHCARE (~2분, 254m), FSCD Burn Unite Hospital (~2분), DR. AZMAL HOSPITAL LTD. (~2분). 참고: Medinova Hospital은 지도상으로는 매우 가까워 보이지만(122m), 실제 도로로는 약 1km 거리에 있으며 이동 시간은 7분입니다.
마지막 문장이 이 프로젝트의 핵심을 담고 있습니다.
세션 2: 벵골어 사용, Claude Code 내부에서
이어서 방글라어(Bangla)로 "저는 Farmgate에 있습니다. 차를 타고 가장 빨리 갈 수 있는 병원은 어디인가요?"라고 질문했습니다. 이번에는 Claude Code가 내장된 MCP 클라이언트를 사용했으며, Geoapify 도구만 허용되었습니다. 이 과정은 4번의 Geoapify 호출과 35초가 소요되었으며 (작성 기록 및 스크립트)를 통해 이루어졌습니다: geocode_address → search_places (15개 장소) → calculate_route_matrix (10개 목표 지점) → include_instructions: true 옵션을 사용한 calculate_route. 이번에는 첫 시도에 바로 성공했습니다.
답변은 방글라어로 돌아왔습니다. 가장 먼저 Al Razi Hospital Complex를 나열했으며(약 1분, 401m, 장소의 opening_hours 기준 "24시간 영업" 표시), 또한 안과 전문 병원인 Al Fateh Diagnostics와 Brain and Mind Hospital는 "일반 병원이 아닐 수 있음"이라는 별도의 목록에 배치했습니다. 지침은 일반적인 알림만 요청했을 뿐인데도, 장소를 이렇게 분류한 것은 에이전트 자체의 판단이었습니다.
하나의 부족한 점: 제 지침에는 "이동 시간 순으로 순위 매기기(rank by travel time)"라고 명시했지만, traffic=approximated를 사용하라는 내용은 없었기 때문에, 에이전트는 자유 흐름 속도(free-flow speeds)를 사용했습니다. 이에 대해 사용자에게 ("교통 상황을 고려하지 않았으므로 실제 시간은 더 오래 걸릴 것입니다") 알려주었습니다. 이제 지침에 해당 내용이 추가되어야 합니다.
세션 3: VS Code + GitHub Copilot에서 동일한 질문 수행
마지막으로, 이번에는 VS Code의 Agent 모드에서 GitHub Copilot을 사용하여 (OpenAI 모델 사용) 처음 했던 질문을 다시 던졌습니다. 이 과정은 작성 기록 및 스크립트를 통해 이루어졌습니다. 업데이트된 지침에 따라 매트릭스와 경로 모두에 traffic: "approximated" 옵션을 적용하여 동일하게 4번의 Geoapify 호출을 수행했습니다. 그 결과는 세션 1의 Claude Code와 동일한 답변이었습니다: AALOK HEALTHCARE, 약 2분, 254m.
Copilot's answer in VS Code
알아두면 좋은 두 가지 놀라운 점:
- Copilot이 권한 프롬프트 후
.vscode/mcp.json 파일을 스스로 읽었습니다. 이로 인해 제가 테스트를 위해 키를 파일에 직접 작성했기 때문에 Geoapify 키가 모델로 전송되었습니다. 이렇게 노출된 키는 교체해야 합니다. 대신 VS Code의 inputs 프롬프트(${input:geoapify-key})를 사용하고, 설정 파일에 대한 파일 읽기 요청은 거부하십시오.
- 어떤 채팅 메시지를 보내기도 전에 MCP 서버에서 Start 버튼을 누르면 "Send a message first." 오류가 발생합니다. 질문만 하면 됩니다. 그러면 서버가 자동으로 시작됩니다.
잘 작동한 부분: MCP 도구들은 모든 곳에 {lat, lon} 객체를 사용했기 때문에 에이전트가 좌표 순서를 혼동하지 않았습니다. 오류 메시지는 한 번의 재시도로 수정할 만큼 충분히 정확했습니다.
인간의 개입이 필요했던 부분: 세션 1에서의 두 가지 실수(4단계와 5단계), 그리고 세션 2에서 누락된 traffic 정보였습니다. 이 각각은 지침에 규칙으로 추가되었습니다: 운전 시에는 _traffic: "approximated"_를 사용하고, 매트릭스 및 경로 모두 동일한 mode와 traffic를 사용하도록 했습니다.
파트 3: OpenAPI 사양을 기반으로 AI 코딩 에이전트가 작성한 CLI
저는 Claude Code에게 네 가지 JSON 사양(Forward Geocoding, Places, Route Matrix, Routing)과 짧은 개요를 제공했습니다. 이 개요는 prompts/01-prototype.md에 있습니다. 그 결과물은 표준 라이브러리만 사용하는 Python 패키지입니다:
python -m dhaka_emergency "Farmgate, Dhaka" --top 2
!! 응급 상황인가요? 먼저 999에 전화하세요.
출발지: Farmgate, Airport Road, Tejgaon, Dhaka - 1215, Bangladesh
...
--map out.html을 추가하면 Geoapify 타일과 첫 번째 결과까지의 경로가 포함된 Leaflet 지도를 얻을 수 있습니다.
HTML map for Farmgate: your position (blue), hospitals (markers) and the route to the fastest one (red)
핵심 원리 (The core of it)
코드의 핵심은 순위 지정(ranking) 단계입니다. 즉, 하나의 경로 행렬(Route Matrix) 요청을 사용하여 단일 출발지(source)와 모든 후보지를 도착지(target)로 설정합니다. 이는 geoapify.py에서 간소화한 내용입니다:
body = {
"mode": mode,
"sources": [{"location": [lon, lat]}]],
...
AI가 명세서(specs)에서 올바르게 파악한 점
- 올바른 서버 사용: 지오코딩(geocoding), 라우팅(routing) 및 행렬 계산을 위한
/v1과 장소 검색(places)을 위한 /v2를 구분하여 사용했습니다.
- 정확한 카테고리 키:
healthcare.hospital, service.fire_station, emergency.ambulance_station 등 정확한 카테고리 키를 사용했습니다.
- 보안 처리 방식: 명세서의
securitySchemes에서 제시된 대로, API 키를 URL에 직접 넣는 대신 x-api-key 헤더를 사용했습니다.
함정: 좌표 순서 (The trap: coordinate order)
REST API들은 좌표 순서에 대해 통일된 기준을 가지고 있지 않습니다. **경로(Routing)**의 waypoints는 위도, 경도 (lat, lon)를 사용하는 반면, **장소(Places)**의 filter/bias와 **경로 행렬(Route Matrix)**의 location은 경도, 위도 (lon, lat)를 사용합니다. 이는 가장 쉽게 버그가 발생할 수 있는 지점입니다. 해결책은 모든 HTTP 호출을 하나의 모듈에 통합하고 각 위치마다 주석을 추가하는 것입니다. (MCP 도구는 이름이 지정된 {lat, lon} 필드를 사용하여 이 문제를 완전히 회피합니다.)
파트 4: 모두를 위한 웹 앱 (A web app for everyone else)
CLI(Command Line Interface)나 AI 클라이언트는 개발자에게 적합하지만, 비상 상황에서는 사람들은 휴대폰을 찾게 됩니다. 따라서 동일한 파이프라인은 단일 정적 페이지(docs/app/, 빌드 단계나 서버가 필요 없음)로도 실행될 수 있으며, GitHub Pages에 무료로 호스팅됩니다:
| 벵골어 (Bangla), 다크 모드, Mirpur 10 근처 병원 | 영어 (English), 라이트 모드, Shahbag 근처 소방서 |
|---|
| Web app in Bangla | Web app in English |
일반적인 '가장 가까운 병원' 검색과 다른 점은 다음과 같습니다: