From f43230ee30ffa2df95e74920b5dd72f83cef11e0 Mon Sep 17 00:00:00 2001 From: najajan Date: Sat, 20 Sep 2025 15:55:25 +0200 Subject: [PATCH] get route --- server/api/service/tools/taxiroute.get.ts | 167 +++++++++++++++++----- 1 file changed, 133 insertions(+), 34 deletions(-) diff --git a/server/api/service/tools/taxiroute.get.ts b/server/api/service/tools/taxiroute.get.ts index 4fddc07..7b7d66c 100644 --- a/server/api/service/tools/taxiroute.get.ts +++ b/server/api/service/tools/taxiroute.get.ts @@ -3,10 +3,8 @@ import https from 'node:https' export default defineEventHandler(async (event) => { const q = getQuery(event) - const oLat = Number(q.origin_lat) - const oLon = Number(q.origin_lng) - const dLat = Number(q.dest_lat) - const dLon = Number(q.dest_lng) + const oLat = Number(q.origin_lat), oLon = Number(q.origin_lng) + const dLat = Number(q.dest_lat), dLon = Number(q.dest_lng) const radius = Number(q.radius ?? 2000) const endpoint = 'https://overpass-api.de/api/interpreter' @@ -20,6 +18,15 @@ export default defineEventHandler(async (event) => { out body; ` + // --- utils --- + const toRad = (d:number)=>d*Math.PI/180 + function haversine(a:{lat:number,lon:number}, b:{lat:number,lon:number}){ + const R=6371000 + const dLat=toRad(b.lat-a.lat), dLon=toRad(b.lon-a.lon) + const la1=toRad(a.lat), la2=toRad(b.lat) + const s=Math.sin(dLat/2)**2 + Math.cos(la1)*Math.cos(la2)*Math.sin(dLon/2)**2 + return 2*R*Math.asin(Math.sqrt(s)) + } function fetchOverpass(query: string) { return new Promise((resolve, reject) => { const req = https.request(endpoint, { @@ -29,7 +36,7 @@ out body; let data = '' res.on('data', d => (data += d)) res.on('end', () => { - if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`)) + if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0,200)}`)) resolve(JSON.parse(data)) }) }) @@ -39,43 +46,135 @@ out body; }) } - function haversine(a: any, b: any) { - const R = 6371000 - const toRad = (d: number) => d * Math.PI / 180 - const dLat = toRad(b.lat - a.lat) - const dLon = toRad(b.lon - a.lon) - const la1 = toRad(a.lat), la2 = toRad(b.lat) - const s = Math.sin(dLat/2)**2 + Math.cos(la1)*Math.cos(la2)*Math.sin(dLon/2)**2 - return 2 * R * Math.asin(Math.sqrt(s)) + // --- build graph from OSM --- + const osm = await fetchOverpass(overpassQ) + + const nodes = new Map() + const ways: Array = [] + for (const el of osm.elements) { + if (el.type === 'node') nodes.set(el.id, { id: el.id, lat: el.lat, lon: el.lon }) + else if (el.type === 'way') ways.push(el) } - function buildGraph(osm: any) { - const nodes = new Map() - const edges: any[] = [] - - for (const el of osm.elements) { - if (el.type === 'node') nodes.set(el.id, { id: el.id, lat: el.lat, lon: el.lon }) + type Edge = { u:number, v:number, w:number, way_id:number, name:string|null } + const edges: Edge[] = [] + for (const w of ways) { + const name: string | null = w.tags?.name || w.tags?.ref || null + for (let i=0;i e.type === 'way')) { - const name = w.tags?.name || w.tags?.ref || null - for (let i = 0; i < w.nodes.length - 1; i++) { - const a = nodes.get(w.nodes[i]) - const b = nodes.get(w.nodes[i + 1]) - if (!a || !b) continue - const d = haversine(a, b) - edges.push({ u: a.id, v: b.id, w: d, way_id: w.id, name }) - edges.push({ u: b.id, v: a.id, w: d, way_id: w.id, name }) + } + + // --- adjacency + quick edge lookup --- + const adj = new Map>() + const edgeName = new Map() + for (const e of edges){ + if (!adj.has(e.u)) adj.set(e.u, []) + adj.get(e.u)!.push({ to:e.v, w:e.w }) + edgeName.set(`${e.u}->${e.v}`, { name:e.name, way_id:e.way_id }) + } + + // --- nearest node to a coordinate --- + function nearestNode(lat:number, lon:number){ + let bestId:number|undefined, bestD=Infinity + for (const n of nodes.values()){ + const d = haversine({lat,lon},{lat:n.lat,lon:n.lon}) + if (d() + const prev = new Map() + const visited = new Set() + const pq: Array<{id:number, d:number}> = [] + + const push = (id:number, d:number)=>{ + pq.push({id,d}) + // simple binary heap-free: insertion sort-ish + pq.sort((a,b)=>a.d-b.d) + } + + for (const id of nodes.keys()) dist.set(id, Infinity) + dist.set(src, 0); push(src,0) + + while (pq.length){ + const {id:u} = pq.shift()! + if (visited.has(u)) continue + visited.add(u) + if (u===dst) break + const lst = adj.get(u); if (!lst) continue + for (const {to:v, w} of lst){ + const alt = dist.get(u)! + w + if (alt < dist.get(v)!){ + dist.set(v, alt) + prev.set(v, u) + push(v, alt) + } } } - return { nodes: [...nodes.values()], edges } + + if (!prev.has(dst) && src!==dst) return null + const path:number[] = [] + let u = dst + path.push(u) + while (u !== src){ + const p = prev.get(u) + if (p===undefined){ break } + u = p + path.push(u) + } + path.reverse() + const total_m = dist.get(dst)! + return { path, total_m } } - const osm = await fetchOverpass(overpassQ) - const graph = buildGraph(osm) + const sp = dijkstra(startAttach.node_id, endAttach.node_id) + if (!sp) { + return { + origin: { lat:oLat, lon:oLon }, + dest: { lat:dLat, lon:dLon }, + start_attach: startAttach, + end_attach: endAttach, + route: null, + names: [] + } + } + + // names along path (consecutive compressed, drop null/unnamed) + const namesSeq: string[] = [] + for (let i=0;i${v}`) || edgeName.get(`${v}->${u}`) + const nm = meta?.name?.trim() + if (nm && (namesSeq.length===0 || namesSeq[namesSeq.length-1]!==nm)) namesSeq.push(nm) + } return { - origin: { lat: oLat, lon: oLon }, - dest: { lat: dLat, lon: dLon }, - graph + origin: { lat:oLat, lon:oLon }, + dest: { lat:dLat, lon:dLon }, + start_attach: startAttach, + end_attach: endAttach, + route: { + node_ids: sp.path, + total_distance_m: sp.total_m + }, + names: namesSeq } })