import { Router } from 'express'; const router = Router(); /** * DEM 표고 프록시 — 브라우저 COEP(require-corp)/CSP(connect-src 'self') 때문에 * 클라이언트가 외부 고도 API를 직접 호출하지 못한다. 서버가 중계해 같은 출처로 응답. * * GET /api/elevation?lat=36.4,36.5&lon=127.4,127.5 (배치, 콤마 구분) * 응답: { elevation: number[] } (입력 순서 보존) * * 1순위 open-topodata SRTM 30m(절벽/지형 더 정밀), 실패 시 open-meteo 90m 폴백. */ router.get('/', async (req, res) => { const latStr = String(req.query.lat ?? req.query.latitude ?? ''); const lonStr = String(req.query.lon ?? req.query.longitude ?? ''); if (!latStr || !lonStr) { res.status(400).json({ error: 'lat/lon required' }); return; } const lats = latStr.split(','); const lons = lonStr.split(','); if (lats.length !== lons.length) { res.status(400).json({ error: 'lat/lon length mismatch' }); return; } // 1) open-topodata SRTM 30m try { const locs = lats.map((la, i) => `${la.trim()},${lons[i].trim()}`).join('|'); const r = await fetch( 'https://api.opentopodata.org/v1/srtm30m?locations=' + encodeURIComponent(locs), ); if (r.ok) { const j: any = await r.json(); if (Array.isArray(j?.results)) { const elevation = j.results.map((x: any) => typeof x?.elevation === 'number' ? x.elevation : null, ); if (elevation.some((v: number | null) => v != null)) { res.json({ elevation, source: 'opentopodata-srtm30m' }); return; } } } } catch { /* 폴백으로 진행 */ } // 2) open-meteo 90m 폴백 try { const r = await fetch( 'https://api.open-meteo.com/v1/elevation?latitude=' + encodeURIComponent(latStr) + '&longitude=' + encodeURIComponent(lonStr), ); if (!r.ok) { res.status(502).json({ error: 'upstream ' + r.status }); return; } const j: any = await r.json(); res.json({ elevation: j?.elevation ?? [], source: 'open-meteo-90m' }); } catch (e) { res.status(502).json({ error: String(e) }); } }); export default router;