"우리 관할에 소화전이 몇 개 있나"는 표로 답하지만, "어디가 부족한가"는 지도라야 답합니다. 소방용수는 개수가 아니라 분포가 중요하기 때문입니다 — 100개가 도심에 몰려 있고 외곽이 비어 있다면, 총량은 충분해도 그 외곽은 위험 구역입니다. 이 안내서는 소방용수 위치와 건물 위치를 공간적으로 겹쳐, 도달 반경 밖 공백 구역을 지도로 찾아냅니다.
데이터와 도구
공공데이터 지도에서 받습니다.
| 데이터 | 출처 | 쓰는 것 |
|---|---|---|
| 전국소방용수시설 표준데이터 | 공공데이터포털 | 소화전·저수조 좌표·유형 |
| 건물/주거지 | 건축물대장·브이월드 | 분석 대상 지점 |
| 행정경계 | SGIS·브이월드 | 관할 범위·집계 단위 |
도구는 레포 레이더의 GEO 클러스터 — geopandas(공간 연산)·folium(지도 시각화)입니다.
1. 좌표계 — 여기서 대부분의 오류가 난다
공간분석의 가장 흔한 실수는 좌표계입니다. 공공데이터의 경위도(위도·경도, EPSG:4326)는 "도(度)" 단위라 그대로 거리를 재면 엉터리가 나옵니다 — "0.003도"는 위치에 따라 실제 거리가 다릅니다. 거리를 재려면 미터 단위 투영좌표계로 변환해야 합니다. 한국 표준은 EPSG:5179(UTM-K)입니다.
import geopandas as gpd # 소방용수·건물을 GeoDataFrame으로 (원본은 보통 경위도 EPSG:4326) water = gpd.read_file("fire_water.geojson").set_crs(4326, allow_override=True) bldg = gpd.read_file("buildings.geojson").set_crs(4326, allow_override=True) # 거리 계산 전 반드시 미터 단위 투영좌표계로 — 한국 표준 UTM-K water_m = water.to_crs(5179) bldg_m = bldg.to_crs(5179)
to_crs(5179) 한 줄을 빠뜨리면 이후 모든 거리·버퍼가 무의미해집니다. 그런데 코드는 에러 없이 돌고 숫자도 그럴듯하게 나옵니다 — 위험도 예측의 데이터 누수처럼, 조용히 틀리는 종류의 실수라 가장 위험합니다. 분석 시작 전 좌표계부터 확인하세요.2. 최근접 거리 — 각 지점에서 가장 가까운 용수까지
각 건물에서 가장 가까운 소방용수까지의 거리를 잽니다. geopandas의 최근접 공간조인이 이걸 한 번에 해 줍니다.
# 각 건물에 가장 가까운 소방용수를 붙이고, 그 거리(m)를 함께 계산 joined = gpd.sjoin_nearest(bldg_m, water_m, how="left", distance_col="용수거리m") print(joined["용수거리m"].describe()) # 분포 — 중앙값·최댓값을 본다 공백 = joined[joined["용수거리m"] > 200] # 도달 기준 초과(예: 200m) print(f"공백 건물 {len(공백)}개 / 전체 {len(joined)}개")
기준 거리(예 200m)는 관할 특성·장비·규정에 맞춰 정합니다. 하나로 고정하지 말고 100·200·300m로 나눠 단계별 공백을 보는 편이 유용합니다.
3. 도달 반경(버퍼) — 용수가 덮는 범위
거꾸로, 각 소방용수가 반경 안에서 덮는 범위를 그려 겹치지 않는 빈 곳을 봅니다. 버퍼(buffer)는 각 점 둘레에 원을 그립니다 — 투영좌표계라 단위가 미터입니다.
covered = water_m.buffer(200).union_all() # 모든 용수의 200m 도달 범위 합집합 # 관할 경계에서 도달 범위를 빼면 = 공백 구역 gap_area = boundary_m.geometry.union_all().difference(covered)
4. 지도로 — 숫자가 아니라 눈으로
공백 건물과 공백 구역을 지도에 얹어야 의사결정자에게 전달됩니다. folium으로 대화형 지도를 만듭니다(시각화는 경위도로 되돌려서).
import folium m = folium.Map(location=[37.55, 126.9], zoom_start=13, tiles="CartoDB positron") # 공백 건물(빨강) · 소방용수(파랑)를 4326으로 되돌려 표시 for _, r in 공백.to_crs(4326).iterrows(): folium.CircleMarker([r.geometry.y, r.geometry.x], radius=3, color="crimson").add_to(m) m.save("소방용수_공백.html")
5. 한계 — 직선거리는 도달시간이 아니다
이 분석의 가장 큰 한계를 분명히 알아야 합니다. 직선거리(as-the-crow-flies)는 소방차 실제 도달거리가 아닙니다. 강·철길·일방통행·경사가 가로막으면 200m 안의 용수도 실제로는 멀 수 있습니다. 더 정밀하게 하려면 도로 네트워크 기반 경로 거리를 써야 하지만, 그건 훨씬 무거운 분석입니다.
| 지표 | 이 분석 | 현실 |
|---|---|---|
| 거리 | 직선 | 도로 경로 |
| 함정 | 강·철길을 무시 | 실제 우회 필요 |
| 용도 | 1차 스크리닝 | 후보 구역 좁히기 |
사람 검토 체크리스트
- [ ] 거리·버퍼 계산 전에 미터 단위 투영좌표계(EPSG:5179)로 변환했습니다. - [ ] 소방용수 표준데이터의 좌표 정확성·최신성을 확인했습니다(폐지·이설 반영). - [ ] 도달 기준 거리를 관할 특성에 맞게 정하고, 여러 단계로 나눠 봤습니다. - [ ] 결과가 직선거리 기반이라는 한계를 보고서에 명시했습니다. - [ ] 공백 구역은 현장 확인 우선순위로만 쓰고 단정하지 않았습니다.