diff --git a/.idea/workspace.xml b/.idea/workspace.xml index cfbe9c4..e237e9d 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -6,10 +6,6 @@ - - - - diff --git a/PublicProperty.js b/PublicProperty.js index 5d5b69d..5d094a9 100644 --- a/PublicProperty.js +++ b/PublicProperty.js @@ -1,9 +1,11 @@ // no dependencies export const __DEBUG__ = 1; -export const __APP_VERSION__ = 'v0.2.0a'; +export const __APP_VERSION__ = 'v0.3.0a'; export const __APP_INTRO__ = ` Algorithm improvement.
-We are introducing a new pre-processing method called Obvious in this version.
+Our new routing solution is based on the Obvious A-Star algorithm now, with 2~10x faster speed in calculations.
+Support concatenating paths.
+You can add points sequentially on the map.
`; \ No newline at end of file diff --git a/api/click.js b/api/click.js index 3d35706..812dd26 100644 --- a/api/click.js +++ b/api/click.js @@ -1,9 +1,9 @@ -import {dijkstra, haversine_distance, obvious_dijkstra} from "../tools/ShortestPath"; +import {dijkstra, haversine_distance, obvious_dijkstra, obvious_a_star} from "../tools/ShortestPath"; import {sill, sill_unwrap, noexcept} from "../tools/Debug"; import {get_row} from "../tools/Misc"; import benchmark from "../tools/PathBench"; -const __spa = obvious_dijkstra; +const __spa = obvious_a_star; function find_nearest_node_id(nodes, point) { const [lat, lon] = point; diff --git a/src/UMap.js b/src/UMap.js index 976b544..4c94e8b 100644 --- a/src/UMap.js +++ b/src/UMap.js @@ -39,7 +39,7 @@ class Markers extends Component { markers: [...prev.markers, [lat, lng]], candMarkers: prev.candMarkers, candEmpty: prev.candEmpty, - polylines: [], // automatically clear polylines when marker changes + polylines: prev.polylines, })); this.getFocus(); } @@ -89,12 +89,12 @@ class Markers extends Component { } } - flushPolylines(pl) { + flushPolylines(pl, clear=true) { this.setState((prev) => ({ markers: prev.markers, candMarkers: prev.candMarkers, candEmpty: prev.candEmpty, - polylines: pl, + polylines: clear ? pl : [...pl, ...prev.polylines], // mind the ordering })); // TODO } @@ -107,7 +107,7 @@ function MapClickHandler({mks,focusUpdater,locator,locker}) { const {lat,lng}=e.latlng; console.info(`Clicking on ${lat} ${lng}`); mks.current.addMarker(lat, lng); - post('POST', 'click', mks.current.state.markers).then((response) => { + post('POST', 'click', mks.current.state.markers.slice(-2)).then((response) => { // TODO: real functionality const pl = JSON.parse(response.multipolyline); // DEBUG @@ -115,7 +115,7 @@ function MapClickHandler({mks,focusUpdater,locator,locker}) { // mks.current.addCandMarker(lat,lon); // }); sill(`pl = ${JSON.stringify(pl)}`); - if (pl.length > 1) mks.current.flushPolylines(pl); + if (pl.length > 1) mks.current.flushPolylines(pl, false); focusUpdater([lat,lng]); locator([lat,lng]); locker(true); diff --git a/tools/PathBench.js b/tools/PathBench.js index cc2e2b2..16f6181 100644 --- a/tools/PathBench.js +++ b/tools/PathBench.js @@ -5,15 +5,25 @@ import {get_row} from "./Misc"; export default function benchmark(nodes, clean_nodes, ways, location, ch_dict, ch_dict_bench, count, aff, actual_start_node_id, actual_end_node_id) { sill(`==========PathBench==========`); let start_time, end_time; - let res; + let res, _; //benchmark Obvious-Dijkstra start_time = performance.now(); res = SP.obvious_dijkstra(clean_nodes, ways, location, 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 Obvious-A-Star + start_time = performance.now(); + res = SP.obvious_a_star(clean_nodes, ways, location, ch_dict, count, aff, actual_start_node_id, actual_end_node_id); + end_time = performance.now(); + sill(`Obvious-A-Star run-time: ${end_time - start_time} ms`); + // benchmark Obvious-Adaptive-A-Star + start_time = performance.now(); + _ = SP.obvious_a_star(clean_nodes, ways, location, ch_dict, count, aff, actual_start_node_id, actual_end_node_id, true); + end_time = performance.now(); + sill(`Obvious-Adaptive-A-Star run-time: ${end_time - start_time} ms`); // benchmark Dijkstra start_time = performance.now(); - const _ = SP.dijkstra(nodes, ways, location, ch_dict_bench, count, aff, actual_start_node_id, actual_end_node_id); + res = SP.dijkstra(nodes, ways, location, ch_dict_bench, count, aff, actual_start_node_id, actual_end_node_id); end_time = performance.now(); sill(`Dijkstra run-time: ${end_time - start_time} ms`); sill(`==============================`); diff --git a/tools/ShortestPath.js b/tools/ShortestPath.js index 94078a1..b28dc39 100644 --- a/tools/ShortestPath.js +++ b/tools/ShortestPath.js @@ -64,7 +64,7 @@ function __dijkstra(nodes, ways, loc, ch, count, aff, u, p) { while (fa[curr]) { curr = fa[curr].toString(); if (vis.has(curr)) { - sill(`Cycle at ${curr}`); + // sill(`Cycle at ${curr}`); break; } vis.add(curr); @@ -125,7 +125,98 @@ function __obvious_dijkstra(nodes, ways, loc, ch, count, aff, u, p) { const prev = curr; curr = fa[curr]; if (vis.has(curr)) { - sill(`Cycle at ${curr}`); + // sill(`Cycle at ${curr}`); + // sill(res); + break; + } + vis.set(curr,true); + + // res.push(curr); + const way_id = find_common(aff[prev], aff[curr]); + const way = ways[way_id]; + if(!way) { + continue; + } + const p_oc = loc[prev][way_id]; + const c_oc = loc[curr][way_id]; + if (p_oc < c_oc) { + for (let _p = p_oc + 1; _p <= c_oc; ++_p) { + res.push(way[_p]); + } + } else { + for (let _p = p_oc - 1; _p >= c_oc; --_p) { + res.push(way[_p]); + } + } + } + // sill("finished Obvious Dijkstra."); + return res; +} + +/** + * Use A-star algorithm with Obvious optimization to find the shortest path. + * @param nodes node index list + * @param ways node list for each way + * @param loc relative location in the 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 + * @param adaptive if the coefficient is hard-coded + */ +function __obvious_a_star(nodes, ways, loc, ch, count, aff, u, p, adaptive = false) { + // sill(`node count: ${Object.keys(nodes).length}`); + const default_coefficient = 1.1; + const linear = (y) => y; + const curve = (y) => 0.8 * y * y; + const inversed_sigmoid = (y) => Math.log(y/(1.0-y)); + const heuristic = (current_distance, node_id) => { + const estimate = haversine_distance(nodes[node_id], nodes[p]); + const coef = (!adaptive) ? default_coefficient : ( + default_coefficient * curve(current_distance / (current_distance + estimate)) + ); + return coef * estimate; + }; + const dis = {}; + const fa = {}; + const vis = new Map(); + const pq = new MinPriorityQueue(); + dis[u] = 0; + pq.push([0, 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); + const t = ch[v].length; + for (let j = 0; j < t; ++j) { + const [c, w] = ch[v][j]; + if (!nodes[c]) continue; + // sill(w); + if ((!dis[c] || d + w < dis[c])) { + dis[c] = d + w; + pq.push([dis[c] + heuristic(dis[c], c), dis[c], c]); + fa[c] = v; + } + } + } + // sill_unwrap(fa); + let curr = p; + const res = [p]; + vis.clear(); + while (fa[curr]) { + const prev = curr; + curr = fa[curr]; + if (vis.has(curr)) { + // sill(`Cycle at ${curr}`); // sill(res); break; } @@ -155,4 +246,5 @@ function __obvious_dijkstra(nodes, ways, loc, ch, count, aff, u, p) { export const haversine_distance = noexcept(__haversine_distance); export const dijkstra = noexcept(__dijkstra); -export const obvious_dijkstra = noexcept(__obvious_dijkstra); \ No newline at end of file +export const obvious_dijkstra = noexcept(__obvious_dijkstra); +export const obvious_a_star = noexcept(__obvious_a_star); \ No newline at end of file