From 4fa95ff78a6545f2b3d89d2a64fe8169b8f86f7a Mon Sep 17 00:00:00 2001 From: arielherself Date: Wed, 27 Dec 2023 14:34:03 +0800 Subject: [PATCH] feat: obvious-dijkstra & attack detection --- .idea/workspace.xml | 12 ++-- api/click.js | 93 ++++++++++++++++++++---------- tools/Debug.js | 17 ++---- tools/Misc.js | 14 +++++ tools/PathBench.js | 21 +++++++ tools/ShortestPath.js | 130 ++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 233 insertions(+), 54 deletions(-) create mode 100644 tools/Misc.js create mode 100644 tools/PathBench.js diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 213e3b6..7018bbc 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,14 +5,12 @@ - - - + + - - - + + diff --git a/api/click.js b/api/click.js index 8877269..d70c937 100644 --- a/api/click.js +++ b/api/click.js @@ -1,12 +1,14 @@ -import {dijkstra, haversine_distance} from "../tools/ShortestPath"; -import {sill, sill_unwrap, get_row, noexcept} from "../tools/Debug"; +import {dijkstra, haversine_distance, obvious_dijkstra} from "../tools/ShortestPath"; +import {sill, sill_unwrap, noexcept} from "../tools/Debug"; +import {get_row} from "../tools/Misc"; +import benchmark from "../tools/PathBench"; -const __spa = dijkstra; +const __spa = benchmark; function find_nearest_node_id(nodes, point) { const [lat, lon] = point; let min_distance = 1e100; - let res = 0; + let res = ''; for (let node_id in nodes) { const curr_distance = haversine_distance(nodes[node_id], point); if (curr_distance < min_distance) { @@ -18,44 +20,71 @@ function find_nearest_node_id(nodes, point) { } const shortest_path = noexcept((nodes, ways, start_point, end_point) => { + // const clean_nodes = nodes; const count = {}; - for(const way_id in ways) { - sill(way_id); - const [l, n] = get_row(ways, way_id); - for(let i = 0; i < n; ++i) { - if(count[l[i]]) { - ++count[l[i]]; - } else { - count[l[i]] = 1; - } - } - } - sill(count); - const ch_dict = {}; + const aff = {}; + const clean_nodes = {}; for (const way_id in ways) { + // sill_unwrap(way_id); + const st = new Set(); const [l, n] = get_row(ways, way_id); for (let i = 0; i < n; ++i) { - if (ch_dict[l[i]]) { - ch_dict[l[i]].push(l[i - 1]); + const curr = l[i]; + if (st.has(curr)) continue; + st.add(curr); + clean_nodes[curr] = nodes[curr]; + if (count[curr]) { + ++count[curr]; } else { - ch_dict[l[i]] = [l[i - 1]]; + count[curr] = 1; } - if (ch_dict[l[i - 1]]) { - ch_dict[l[i - 1]].push(l[i]); + if (aff[curr]) { + aff[curr][way_id] = true; } else { - ch_dict[l[i - 1]] = [l[i]]; + aff[curr] = {[way_id]: true}; } } } - const clean_nodes = {}; - Object.keys(nodes).forEach((node_id) => { - if (ch_dict[node_id]) clean_nodes[node_id] = nodes[node_id]; - }); - sill_unwrap(start_point); + sill_unwrap(aff); const actual_start_node_id = find_nearest_node_id(clean_nodes, start_point); const actual_end_node_id = find_nearest_node_id(clean_nodes, end_point); + sill_unwrap(typeof (actual_start_node_id)); + // sill(count); + const ch_dict = {}; + let f = 1; + for (const t in ways) { + if (t === ways[aff[actual_start_node_id]]) sill('yes'); + const [l, n] = get_row(ways, t); + let prev = ''; + for (let i = 0; i < n; ++i) { + const curr = l[i].toString(); + // if(true) { + if (count[curr] > 1 || curr === actual_end_node_id || curr === actual_start_node_id) { + if (curr === actual_start_node_id) sill(curr === actual_start_node_id); + if (prev !== '') { + if (ch_dict[curr]) { + ch_dict[curr].push(prev); + } else { + ch_dict[curr] = [prev]; + } + if (ch_dict[prev]) { + ch_dict[prev].push(curr); + } else { + ch_dict[prev] = [curr]; + } + } + prev = curr; + } + } + } + // const clean_nodes = {}; + // Object.keys(nodes).forEach((node_id) => { + // if (ch_dict[node_id]) clean_nodes[node_id] = nodes[node_id]; + // }); + sill(`start distance: ${haversine_distance(nodes[actual_start_node_id], start_point)}`); + sill(`dest distance: ${haversine_distance(nodes[actual_end_node_id], end_point)}`); sill("calling __spa..."); - const seq = __spa(clean_nodes, ch_dict, actual_start_node_id, actual_end_node_id); + const seq = __spa(nodes, clean_nodes, ways, ch_dict, count, aff, actual_start_node_id, actual_end_node_id); const res = [end_point]; seq.forEach((node_id) => { if (clean_nodes[node_id]) res.push(clean_nodes[node_id]); @@ -71,13 +100,17 @@ export default function handler(req, res) { lonRange = pts.map((row) => row[1]); const minlon = Math.min(...lonRange) - 0.01, minlat = Math.min(...latRange) - 0.01, maxlon = Math.max(...lonRange) + 0.01, maxlat = Math.max(...latRange) + 0.01; + if (haversine_distance([minlat, minlon], [maxlat, maxlon]) > 100) { + res.status(500); + return; + } const request_uri = `https://www.overpass-api.de/api/interpreter?data=[out:json];way[highway](${minlat},${minlon},${maxlat},${maxlon});(._;>;);out body;`; sill(`Requesting ${request_uri}`); const fetch_debug_response = fetch(request_uri).then((response) => { return response.json(); }); fetch_debug_response.then((debug_response) => { - sill(debug_response); + // sill(debug_response); let ps = {}; let ws = {}; debug_response.elements.forEach((it) => { diff --git a/tools/Debug.js b/tools/Debug.js index 665bf35..d85a1c9 100644 --- a/tools/Debug.js +++ b/tools/Debug.js @@ -1,7 +1,7 @@ // no dependencies import {__DEBUG__} from "../PublicProperty"; -export function noexcept(f) { +export function noexcept(_f) { /** * No exceptions. * @returns {*} @@ -9,7 +9,7 @@ export function noexcept(f) { */ function _wrap() { try { - return f.apply(this, arguments); + return _f.apply(this, arguments); } catch (e) { console.debug(e); } @@ -17,17 +17,12 @@ export function noexcept(f) { return _wrap; } -export const sill = noexcept((x) => { +export const sill = noexcept((_x) => { if(__DEBUG__) { - console.log(x); + console.log(_x); } }); -export const sill_unwrap = noexcept((x) => { - sill(JSON.stringify(x)); -}); - -export const get_row = noexcept((a,i) => { - const row = a[i]; - return [row, row.length]; +export const sill_unwrap = noexcept((_x) => { + sill(JSON.stringify(_x)); }); \ No newline at end of file diff --git a/tools/Misc.js b/tools/Misc.js new file mode 100644 index 0000000..e5a89e2 --- /dev/null +++ b/tools/Misc.js @@ -0,0 +1,14 @@ +import {noexcept, sill, sill_unwrap} from "./Debug"; + +export const get_row = noexcept((_a,_i) => { + const row = _a[_i]; + return [row, row.length]; +}); + +export const find_common = noexcept((_iterable_1, _iterable_2) => { + for(const [k,_] of Object.entries(_iterable_2)) { + if (_iterable_1[k] === true) { + return k; + } + } +}); diff --git a/tools/PathBench.js b/tools/PathBench.js new file mode 100644 index 0000000..c5fcd93 --- /dev/null +++ b/tools/PathBench.js @@ -0,0 +1,21 @@ +import * as SP from "./ShortestPath"; +import {sill} from "./Debug"; +import {get_row} from "./Misc"; + +export default function benchmark(nodes, clean_nodes, ways, ch_dict, count, aff, actual_start_node_id, actual_end_node_id) { + sill(`==========PathBench==========`); + let start_time, end_time; + let res; + //benchmark Obvious-Dijkstra + start_time = performance.now(); + res = SP.obvious_dijkstra(clean_nodes, ways, ch_dict, count, aff, actual_start_node_id, actual_end_node_id); + end_time = performance.now(); + sill(`Obvious-Dijkstra run-time: ${end_time - start_time} ms`); + // benchmark Dijkstra + start_time = performance.now(); + res = SP.dijkstra(nodes, ways, ch_dict, count, aff, actual_start_node_id, actual_end_node_id); + end_time = performance.now(); + sill(`Dijkstra run-time: ${end_time - start_time} ms`); + sill(`==============================`); + return res; +} \ No newline at end of file diff --git a/tools/ShortestPath.js b/tools/ShortestPath.js index e75d286..b20f638 100644 --- a/tools/ShortestPath.js +++ b/tools/ShortestPath.js @@ -1,5 +1,6 @@ import {MinPriorityQueue} from "@datastructures-js/priority-queue"; -import {sill, noexcept} from "./Debug"; +import {sill, noexcept, sill_unwrap} from "./Debug"; +import {find_common} from "./Misc"; function __haversine_distance(p1, p2) { const toRadians = (degrees) => { @@ -22,17 +23,20 @@ function __haversine_distance(p1, p2) { /** * Use Dijkstra algorithm to find the shortest path. * @param nodes node index list + * @param ways unused in this implementation * @param ch adjacent table + * @param count unused in this implementation + * @param aff unused in this implementation * @param u start node id * @param p destination node id */ -function __dijkstra(nodes, ch, u, p) { - sill(`node count: ${Object.keys(nodes).length}`); +function __dijkstra(nodes, ways, ch, count, aff, u, p) { + // sill(`node count: ${Object.keys(nodes).length}`); const weight_cache = {}; const get_weight = (n1, n2) => { const tup = [n1, n2]; if (weight_cache[tup]) { - return weight_cache[[n1, n2]]; + return weight_cache[tup]; } weight_cache[tup] = __haversine_distance(nodes[n1], nodes[n2]); return weight_cache[tup]; @@ -46,6 +50,9 @@ function __dijkstra(nodes, ch, u, p) { while (!pq.isEmpty()) { const [d, v] = pq.pop(); if (vis.has(v) || !ch[v]) continue; + if (v === p) { + break; + } vis.add(v); const t = ch[v].length; for (let j = 0; j < t; ++j) { @@ -71,9 +78,120 @@ function __dijkstra(nodes, ch, u, p) { vis.add(curr); res.push(curr); } - sill("finished Dijkstra."); + // sill("finished Dijkstra."); + return res; +} + +/** + * Use Dijkstra algorithm with Obvious optimization to find the shortest path. + * @param nodes node index list + * @param ways node list for each way + * @param ch adjacent table + * @param count determine isolated nodes + * @param aff affiliation of isolated nodes + * @param u start node id + * @param p destination node id + */ +function __obvious_dijkstra(nodes, ways, ch, count, aff, u, p) { + // sill(`node count: ${Object.keys(nodes).length}`); + const weight_cache = {}; + const get_weight_along = (way, n1, n2) => { + const tup = [n1, n2]; + if (weight_cache[tup]) { + return weight_cache[tup]; + } + let res = 0; + const n = way.length; + let prev = ''; + let state = 0; + for (let i = 0; i < n; ++i) { + const curr = way[i].toString(); + if (curr === n1 || curr === n2) { + if (state) { + res += (__haversine_distance(nodes[prev], nodes[curr])); + break; + } else { + state = 1; + prev = curr; + } + } else if (state) { + res += (__haversine_distance(nodes[prev], nodes[curr])); + prev = curr; + } + } + weight_cache[tup] = res; + return res; + }; + const dis = {}; + const fa = {}; + const vis = new Map(); + const pq = new MinPriorityQueue(); + dis[u] = 0; + pq.push([0, u]); + while (!pq.isEmpty()) { + const [d, v] = pq.pop(); + if (vis.has(v) || !ch[v]) { + // sill(!ch[v]); + continue; + } + if (v === p) { + // sill('break'); + break; + } + // sill('loop'); + vis.set(v,true); + dis[v] = d; + const t = ch[v].length; + for (let j = 0; j < t; ++j) { + const c = ch[v][j]; + if (!nodes[c]) continue; + const way_id = find_common(aff[c], aff[v]); + if (!way_id) sill("FATAL: NO COMMON WAY"); + const way = ways[way_id]; + const w = get_weight_along(way, v, c); + // sill(w); + if (w > 0 && (!dis[c] || d + w < dis[c])) { + dis[c] = d + w; + pq.push([dis[c], c]); + const v_oc = way.indexOf(parseInt(v)); + const c_oc = way.indexOf(parseInt(c)); + let prev = c; + if (v_oc < c_oc) { + for (let _p = c_oc - 1; _p >= v_oc; --_p) { + const node = way[_p].toString(); + fa[prev] = node; + if (vis.has(node)) break; + prev = node; + } + } else { + for (let _p = c_oc + 1; _p <= v_oc; ++_p) { + const node = way[_p].toString(); + fa[prev] = node; + if (vis.has(node)) break; + prev = node; + } + } + } + } + } + // sill_unwrap(fa); + let curr = p; + const res = [p]; + vis.clear(); + while (fa[curr]) { + curr = fa[curr].toString(); + if (vis.has(curr)) { + sill(`Cycle at ${curr}`); + // sill(res); + break; + } + vis.set(curr,true); + res.push(curr); + } + // sill("finished Obvious Dijkstra."); return res; } export const haversine_distance = noexcept(__haversine_distance); -export const dijkstra = noexcept(__dijkstra); \ No newline at end of file +export const dijkstra = noexcept(__dijkstra); +export const obvious_dijkstra = noexcept(__obvious_dijkstra); \ No newline at end of file