diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 5109b78..213e3b6 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,13 +5,13 @@ - - - + + + - - + + - { + "keyToString": { + "Node.js.test.js.executor": "Run", + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "WebServerToolWindowFactoryState": "false", + "git-widget-placeholder": "dev", + "last_opened_file_path": "/home/user/Documents/fd_data-structures/pj/frontend", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_interpreter_path": "/home/user/.nvm/versions/node/v20.10.0/bin/node", + "nodejs_package_manager_path": "npm", + "npm.start.executor": "Run", + "settings.editor.selected.configurable": "editor.preferences.fonts.default", + "ts.external.directory.path": "/home/user/.local/share/JetBrains/Toolbox/apps/webstorm/plugins/javascript-impl/jsLanguageServicesImpl/external", + "vue.rearranger.settings.migration": "true" } -}]]> +} @@ -93,7 +93,9 @@ - + + + diff --git a/PublicProperty.js b/PublicProperty.js new file mode 100644 index 0000000..7c26357 --- /dev/null +++ b/PublicProperty.js @@ -0,0 +1,9 @@ +// no dependencies + +export const __DEBUG__ = 1; +export const __APP_VERSION__ = 'v0.1.3a'; + +export const __APP_INTRO__ = ` +Right way to follow.
+In this update you can find a shortest route for any given start and destination.
+`; \ No newline at end of file diff --git a/api/click.js b/api/click.js index 7651363..8877269 100644 --- a/api/click.js +++ b/api/click.js @@ -1,21 +1,7 @@ -import {MinPriorityQueue} from "@datastructures-js/priority-queue"; +import {dijkstra, haversine_distance} from "../tools/ShortestPath"; +import {sill, sill_unwrap, get_row, noexcept} from "../tools/Debug"; -function haversine_distance(p1,p2) { - const toRadians = (degrees) => { - return degrees * Math.PI / 180; - }; - const [lat1, lon1] = p1; - const [lat2, lon2] = p2; - const R = 6371; - const dLat = toRadians(lat2 - lat1); - const dLon = toRadians(lon2 - lon1); - const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + - Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) * - Math.sin(dLon / 2) * Math.sin(dLon / 2); - const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - if (R * c < 0) console.log("WARNING!!!"); - return R * c; -} +const __spa = dijkstra; function find_nearest_node_id(nodes, point) { const [lat, lon] = point; @@ -31,139 +17,84 @@ function find_nearest_node_id(nodes, point) { return res; } -function __spa(nodes, ch, u, p) { - console.log(`node count: ${Object.keys(nodes).length}`); - const weight_cache = {}; - try { - const get_weight = (n1, n2) => { - const tup = [n1, n2]; - if (weight_cache[tup]) { - return weight_cache[[n1, n2]]; - } - // console.log("21"); - // console.log(`${nodes[n1]} ${nodes[n2]}`); - weight_cache[tup] = haversine_distance(nodes[n1], nodes[n2]); - return weight_cache[tup]; - }; - const dis = {}; - const fa = {}; - const vis = new Set(); - const pq = new MinPriorityQueue(); - // console.log(`nodes: ${JSON.stringify(nodes)}`); - dis[u] = 0; - pq.push([0, u]); - while (!pq.isEmpty()) { - const [d, v] = pq.pop(); - if (vis.has(v) || !ch[v]) continue; - vis.add(v); - const t = ch[v].length; - for (let j = 0; j < t; ++j) { - const c = ch[v][j]; - if (!nodes[c]) continue; - const w = get_weight(v, c); - if (!dis[c] || d + w < dis[c]) { - dis[c] = d + w; - pq.push([dis[c], c]); - fa[c] = v; - } +const shortest_path = noexcept((nodes, ways, start_point, end_point) => { + 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; } } - // console.log(`fa = ${JSON.stringify(fa)}`); - let curr = p; - const res = [p]; - // console.log(JSON.stringify(p)); - vis.clear(); - while (fa[curr]) { - // console.log(JSON.stringify(curr)); - curr = fa[curr].toString(); - if(vis.has(curr)) { - console.log(`Cycle at ${curr}`); - break; - } - vis.add(curr); - res.push(curr); - } - console.log("finished __spa."); - // console.log(JSON.stringify(res)); - return res; - } catch (e) { - console.log(e); } -} - -function shortest_path(nodes, ways, start_point, end_point){ - try { // console.log(`Calling shortest_path(${nodes},${ways},${start_point},${end_point})`); - const ch_dict = {}; - for (const way_id in ways) { - const l = ways[way_id]; - const n = l.length; - for (let i = 1; i < n; ++i) { - if (ch_dict[l[i]]) { - ch_dict[l[i]].push(l[i - 1]); - } else { - ch_dict[l[i]] = [l[i - 1]]; - } - if (ch_dict[l[i - 1]]) { - ch_dict[l[i - 1]].push(l[i]); - } else { - ch_dict[l[i - 1]] = [l[i]]; - } + sill(count); + const ch_dict = {}; + for (const way_id in ways) { + 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]); + } else { + ch_dict[l[i]] = [l[i - 1]]; + } + if (ch_dict[l[i - 1]]) { + ch_dict[l[i - 1]].push(l[i]); + } else { + ch_dict[l[i - 1]] = [l[i]]; } } - // console.log(ch_dict); - const clean_nodes = {}; - Object.keys(nodes).forEach((node_id) => { - if (ch_dict[node_id]) clean_nodes[node_id] = nodes[node_id]; - }); - 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); - console.log("calling __spa..."); - const seq = __spa(clean_nodes, ch_dict, 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]); - }); - res.push(start_point); - return res; - } catch (e) { - console.log(e); } -} + 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); + 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("calling __spa..."); + const seq = __spa(clean_nodes, ch_dict, 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]); + }); + res.push(start_point); + return res; +}); -export default function handler(req,res){ - const pts=JSON.parse(req.body); - const latRange=pts.map((row)=>row[0]), - 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; - // console.log(`1+1`); - const request_uri=`https://www.overpass-api.de/api/interpreter?data=[out:json];way[highway](${minlat},${minlon},${maxlat},${maxlon});(._;>;);out body;`; - console.log(`Requesting ${request_uri}`); - const fetch_debug_response= fetch(request_uri).then((response)=>{ +export default function handler(req, res) { + const pts = JSON.parse(req.body); + const latRange = pts.map((row) => row[0]), + 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; + 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)=>{ - console.log(debug_response); + fetch_debug_response.then((debug_response) => { + sill(debug_response); let ps = {}; let ws = {}; - debug_response.elements.forEach((it)=> { + debug_response.elements.forEach((it) => { if (it.type === "node") { - ps[it.id] = [it.lat,it.lon]; + ps[it.id] = [it.lat, it.lon]; } else if (it.type === "way") { ws[it.id] = it.nodes; } }); - // console.log(`pts[0]: ${pts[0]}`); - const path_found = shortest_path(ps,ws,pts[0],pts[pts.length - 1]); - // const path_found = []; - // console.log(JSON.stringify(path_found)); + const path_found = shortest_path(ps, ws, pts[0], pts[pts.length - 1]); res.status(200).json({ log: `Method: click\nArgs: ${pts}\nStatus: requested "${request_uri}", got response ${JSON.stringify(debug_response.elements)}`, multipolyline: JSON.stringify(path_found), // __debug_pts: ps }); - }).catch(e=>{ + }).catch(e => { + console.debug(e); res.status(500); }); diff --git a/api/handshake.js b/api/handshake.js index 2421e38..d0c3898 100644 --- a/api/handshake.js +++ b/api/handshake.js @@ -1,9 +1,4 @@ -const __DEBUG__=1, - __APP_VERSION__='v0.1.3a', - __APP_INTRO__=` -Right way to follow.
-In this update you can find a shortest route for any given start and destination.
-`; +import {__APP_VERSION__, __APP_INTRO__} from "../PublicProperty"; export default function handler(req,res){ res.status(200).json({ diff --git a/src/Networking.js b/src/Networking.js index 9caf9b8..5df9f74 100644 --- a/src/Networking.js +++ b/src/Networking.js @@ -1,3 +1,5 @@ +import {sill} from "./tools/Debug"; + export function post(type,method,args) { let ft = async () => { let response; @@ -13,7 +15,7 @@ export function post(type,method,args) { }); } const response_data = await response.json(); - console.log(`Request "${method}" finished:\n ${response_data.log.replaceAll('\n','\n ')}`); + sill(`Request "${method}" finished:\n ${response_data.log.replaceAll('\n','\n ')}`); return response_data; }; return ft(); diff --git a/src/UMap.js b/src/UMap.js index 42f484e..976b544 100644 --- a/src/UMap.js +++ b/src/UMap.js @@ -6,6 +6,7 @@ import {post} from './Networking'; import {Autocomplete, CircularProgress, Sheet} from "@mui/joy"; import SimulateClick from "./SimulateClick"; import {NearMe} from "@mui/icons-material"; +import {sill} from "./tools/Debug"; const defaultZoomLevel=16; const defaultLocationName = 'Apple Park'; @@ -113,7 +114,7 @@ function MapClickHandler({mks,focusUpdater,locator,locker}) { // response.__debug_pts.forEach(({lat,lon})=>{ // mks.current.addCandMarker(lat,lon); // }); - console.log(`pl = ${JSON.stringify(pl)}`); + sill(`pl = ${JSON.stringify(pl)}`); if (pl.length > 1) mks.current.flushPolylines(pl); focusUpdater([lat,lng]); locator([lat,lng]); @@ -215,7 +216,7 @@ export default function UMap() { const [zoom,setZoom] = useState(defaultZoomLevel); const sf = (a) => { setFocus(a); - console.log(`triggered focus update, new focus is ${focus}`); + sill(`triggered focus update, new focus is ${focus}`); const ft = fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${a[0]}&lon=${a[1]}&zoom=${zoom}&addressdetails=0`).then(response => response.json()); ft.then((response) => setNearbyName(' ' + response.name)).catch(() => setNearbyName('by')); }; diff --git a/src/tools b/src/tools new file mode 120000 index 0000000..4887d6e --- /dev/null +++ b/src/tools @@ -0,0 +1 @@ +../tools \ No newline at end of file diff --git a/tools/Debug.js b/tools/Debug.js new file mode 100644 index 0000000..665bf35 --- /dev/null +++ b/tools/Debug.js @@ -0,0 +1,33 @@ +// no dependencies +import {__DEBUG__} from "../PublicProperty"; + +export function noexcept(f) { + /** + * No exceptions. + * @returns {*} + * @private + */ + function _wrap() { + try { + return f.apply(this, arguments); + } catch (e) { + console.debug(e); + } + } + return _wrap; +} + +export const sill = noexcept((x) => { + if(__DEBUG__) { + 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]; +}); \ No newline at end of file diff --git a/tools/ShortestPath.js b/tools/ShortestPath.js new file mode 100644 index 0000000..e75d286 --- /dev/null +++ b/tools/ShortestPath.js @@ -0,0 +1,79 @@ +import {MinPriorityQueue} from "@datastructures-js/priority-queue"; +import {sill, noexcept} from "./Debug"; + +function __haversine_distance(p1, p2) { + const toRadians = (degrees) => { + return degrees * Math.PI / 180; + }; + const [lat1, lon1] = p1; + const [lat2, lon2] = p2; + const R = 6371; + const dLat = toRadians(lat2 - lat1); + const dLon = toRadians(lon2 - lon1); + const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) * + Math.sin(dLon / 2) * Math.sin(dLon / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + if (R * c < 0) console.warn("WARNING!!!"); + return R * c; +} + + +/** + * Use Dijkstra algorithm to find the shortest path. + * @param nodes node index list + * @param ch adjacent table + * @param u start node id + * @param p destination node id + */ +function __dijkstra(nodes, ch, 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]]; + } + weight_cache[tup] = __haversine_distance(nodes[n1], nodes[n2]); + return weight_cache[tup]; + }; + const dis = {}; + const fa = {}; + const vis = new Set(); + 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]) continue; + vis.add(v); + const t = ch[v].length; + for (let j = 0; j < t; ++j) { + const c = ch[v][j]; + if (!nodes[c]) continue; + const w = get_weight(v, c); + if (!dis[c] || d + w < dis[c]) { + dis[c] = d + w; + pq.push([dis[c], c]); + fa[c] = v; + } + } + } + let curr = p; + const res = [p]; + vis.clear(); + while (fa[curr]) { + curr = fa[curr].toString(); + if (vis.has(curr)) { + sill(`Cycle at ${curr}`); + break; + } + vis.add(curr); + res.push(curr); + } + sill("finished Dijkstra."); + return res; +} + +export const haversine_distance = noexcept(__haversine_distance); +export const dijkstra = noexcept(__dijkstra); \ No newline at end of file