Gildle 여름 그늘 경로 — 건물 그림자를 미리 계산하는 라우팅

요약

영역 내용
기능 summer_shade 모드 — 출발 시각의 태양 위치·건물 그림자·가로수를 결합한 그늘 우선 경로
데이터 서울 건물 245,885동(OSM) 그림자를 13개 시간 슬롯으로 사전 계산 (17MB)
정확도 높이 태그 결측 75.7% → 250m 격자 중앙값 imputation으로 추정
보안(별건) LLM 챗 3종 require_admin 잠금 + 공개 데모 페이지 게이트 정리

뚜벅 방식의 그늘 라우팅

뚜벅(ttubeok)처럼 “그늘로만 걷는 경로”를 만들고 싶었다. 접근은 사전 계산이다:

  1. 태양 위치 — NOAA 근사식을 순수 파이썬 40줄로 구현. 외부 API 없이 위도·경도·시각으로 고도·방위각을 계산한다.
  2. 건물 그림자 — Overpass로 서울 전역 건물 풋프린트+높이를 수집하고, 대표 한여름 날짜의 07~19시 정시마다 그림자 폴리곤(풋프린트 ∪ 그림자 방향 평행이동본의 convex hull)을 캐스팅한다.
  3. 엣지 교차 — shapely STRtree로 보행 그래프 233,964개 엣지와 교차 길이 비율을 구해 “엣지별 × 시간대별 그늘 %”를 JSON으로 저장한다.

런타임은 조회만 한다. /navigate가 출발 시각을 슬롯에 매핑해 그늘 lookup을 만들고, 다익스트라 가중치를 거리 × (1 + 4.0 × (1 - 그늘))로 준다 — 완전 햇빛 구간은 5배 페널티라 그늘 우회가 강하게 선호되지만 경로는 항상 나온다.

계산된 분포는 물리적으로 정직한 U자 곡선이었다: 정오 평균 그늘 16~17%(짧은 그림자), 07시 54%·19시 73%(긴 그림자).

밤은 실제 태양 고도로 판정

“해가 지면 그늘 계산이 무의미하다”는 요구는 고정 시간 경계 대신 요청 시각의 실제 태양 고도로 풀었다. 고도 ≤ 0°면 그늘 가중을 끄고 최단 경로 + night: true를 반환한다. 일출·일몰 API를 받아올 필요 없이 이미 있는 태양 위치 수식이 계절 변화를 자동으로 따라간다.

높이 결측 75.7%와 격자 중앙값 모델

여의도 샘플에선 높이 태그 보유율이 83%였는데 서울 전역은 24%뿐이었다. 결측을 전부 기본값 6m로 두면 고층 그림자가 크게 과소평가된다.

딥러닝 그림자 세그멘테이션은 기각했다 — 위성사진에서 배우는 건 “촬영된 그 시각”의 그림자뿐이라 시간대별 예측 목적과 안 맞는다. 대신 250m 격자별로 실측 높이의 중앙값을 구해 같은 격자의 결측 건물에 적용했다(표본 3동 미만이면 전역 중앙값, 상한 150m). 실측 66,273동으로 179,612동을 추정했고, 저층 주거지와 고층 밀집지의 동네 특성이 그대로 반영된다. 순수 파이썬 수십 줄로 KNN 회귀와 실질 동급이다.

Overpass에게 차단당한 이야기

첫 수집은 1초 간격으로 돌리다 53타일 만에 429 → IP 수준 연결 차단을 당했다. 재설계: 미러 3곳 순환(실측으로 안정 확인된 osm.fr 우선), 지수 백오프 3단(15s→60s→180s), 타일 단위 저장 + 사이드카 이어받기. 재실행은 완료 타일을 건너뛰므로 어디서 끊겨도 유실이 없다. 2회차 수집은 308타일 실패 0으로 완주했다.

덤으로 배운 것: 기본 python-requests UA는 Overpass가 406으로 거부하고, kumi.systems 미러는 알고 보니 private.coffee의 DNS 별칭이었다.

검증

같은 여의도 출발·도착으로 시간만 바꿔 호출한 E2E:

출발 시각 경로 그늘 비율
08:00 36노드 (우회) 90%
13:00 28노드 (최단 가까움) 66%
17:00 36노드 (다른 우회) 76%
22:00 32노드 (밤 — 최단) —

시간대에 따라 실제로 다른 경로가 나오고, 프로덕션 배포 후 동일 결과를 재확인했다. 프론트는 그늘 구간을 초록, 햇빛 구간을 주황으로 칠하고 그늘 비율 %를 보여준다. 테스트는 태양 위치·그림자 기하·가중치·API까지 20건 신규, gildle 전체 198건 통과.

별건: 무인증으로 열려 있던 LLM 챗 3종 잠금

같은 날 오전, 백로그에 있던 보안 건도 정리했다. titanic·langchain·soccer 챗 백엔드가 인증·rate limit 없이 인터넷에 공개돼 있어 URL만 알면 Gemini를 대신 호출할 수 있었다. 셋 다 require_admin으로 잠그고, 이 API를 쓰던 사이트 전역 플로팅 챗 위젯은 관리자 전용 노출로 전환했다. /mail·/dispatch·/telegram 데모 페이지도 “공개 페이지 ↔ 잠긴 백엔드” 반쪽 상태를 AdminAuthGate로 정리 — 이 패턴은 이제 저장소 전체에서 소멸했다.