Merge pull request #1 from arielherself/dev

v0.3.1-beta
This commit is contained in:
Ariel 2023-12-30 11:28:07 +08:00 committed by GitHub
commit 7531f29218
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 12749 additions and 1768 deletions

1
.babelrc Normal file
View File

@ -0,0 +1 @@
{ "presets": ["@babel/preset-flow"] }

1
.gitignore vendored
View File

@ -16,3 +16,4 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
.vercel

12
.idea/frontend.iml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/misc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/frontend.iml" filepath="$PROJECT_DIR$/.idea/frontend.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -1,62 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="3c7078e7-6f30-4d92-9696-11496f9e6dff" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/nanomap/.idea/.gitignore" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/.idea/jsLibraryMappings.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/.idea/modules.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/.idea/nanomap.iml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/.idea/vcs.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/nanomap/.gitignore" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/nanomap/README.md" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/nanomap/package-lock.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/nanomap/package.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/nanomap/public/favicon.ico" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/nanomap/public/index.html" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/nanomap/src/App.css" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/nanomap/src/App.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/nanomap/src/App.test.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/nanomap/src/Networking.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/nanomap/src/SimulateClick.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/nanomap/src/UMap.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/nanomap/src/index.css" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/nanomap/src/index.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/nanomap/nanomap/src/logo.svg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="JavaScript File" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="ProjectColorInfo"><![CDATA[{
"associatedIndex": 3
}]]></component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 3
}</component>
<component name="ProjectId" id="2Yc9rUDmr9wvYpz7Xdq1eJVJoJy" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"WebServerToolWindowFactoryState": "false",
"git-widget-placeholder": "dev",
"last_opened_file_path": "/home/user/Documents/fd_data-structures/pj/nanomap-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_package_manager_path": "npm",
"vue.rearranger.settings.migration": "true"
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;Node.js.test.js.executor&quot;: &quot;Run&quot;,
&quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;WebServerToolWindowFactoryState&quot;: &quot;false&quot;,
&quot;git-widget-placeholder&quot;: &quot;dev&quot;,
&quot;last_opened_file_path&quot;: &quot;/home/user/Documents/fd_data-structures/pj/frontend&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_interpreter_path&quot;: &quot;/home/user/.nvm/versions/node/v20.10.0/bin/node&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;npm.start.executor&quot;: &quot;Run&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;editor.preferences.fonts.default&quot;,
&quot;ts.external.directory.path&quot;: &quot;/home/user/.local/share/JetBrains/Toolbox/apps/webstorm/plugins/javascript-impl/jsLanguageServicesImpl/external&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}
}]]></component>
}</component>
<component name="RunManager">
<configuration name="start" type="js.build_tools.npm" temporary="true" nameIsGenerated="true">
<package-json value="$PROJECT_DIR$/package.json" />
@ -83,6 +79,16 @@
<option name="presentableId" value="Default" />
<updated>1700814200305</updated>
<workItem from="1700814201341" duration="315000" />
<workItem from="1700814808669" duration="47000" />
<workItem from="1700822693197" duration="9493000" />
<workItem from="1701004587189" duration="3578000" />
<workItem from="1701042611758" duration="10222000" />
<workItem from="1701056208994" duration="5687000" />
<workItem from="1701847206194" duration="6179000" />
<workItem from="1702448962541" duration="5620000" />
<workItem from="1703419885970" duration="13073000" />
<workItem from="1703582457934" duration="210000" />
<workItem from="1703642799206" duration="22143000" />
</task>
<servers />
</component>

13
PublicProperty.js Normal file
View File

@ -0,0 +1,13 @@
// no dependencies
export const __DEBUG__ = 0;
export const __APP_VERSION__ = 'v0.3.1-beta';
export const __APP_INTRO__ = `
<b>Algorithm improvement.</b><br>
Our new routing solution is based on the Obvious A-Star algorithm now, with 2~10x faster speed in calculations.<br>
<b>Support concatenating paths.</b><br>
You can add points sequentially on the map.<br>
<b>Clear the map.</b><br>
Clear all markers with one click.<br>
`;

1631
README.md

File diff suppressed because it is too large Load Diff

172
api/click.js Normal file
View File

@ -0,0 +1,172 @@
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_a_star;
function find_nearest_node_id(nodes, point) {
const [lat, lon] = point;
let min_distance = 1e100;
let res = '';
for (let node_id in nodes) {
const curr_distance = haversine_distance(nodes[node_id], point);
if (curr_distance < min_distance) {
min_distance = curr_distance;
res = node_id;
}
}
return res;
}
const shortest_path = noexcept((nodes, ways, start_point, end_point) => {
// const clean_nodes = nodes;
const count = {};
const aff = {};
const location = {};
const clean_nodes = {};
// TODO: delete this
let count1 = 0, tot = 0;
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) {
l[i] = l[i].toString(); // critical
const curr = l[i];
if (st.has(curr)) continue;
st.add(curr);
if (location[curr]) {
location[curr][way_id] = i;
} else {
location[curr] = {[way_id]: i};
}
clean_nodes[curr] = nodes[curr];
if (count[curr]) {
if (count[curr] === 1) {
count1 -= 1;
}
++count[curr];
} else {
count[curr] = 1;
count1 += 1;
tot += 1;
}
if (aff[curr]) {
aff[curr][way_id] = true;
} else {
aff[curr] = {[way_id]: true};
}
}
}
sill(`One ratio: ${count1} / ${tot}`);
// 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 = {};
const ch_dict_bench = {};
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);
// // TODO: delete this
// for (let i = 1; i < n; ++i) {
// const curr = l[i];
// const prev = l[i-1];
// const distance = haversine_distance(nodes[curr], nodes[prev]);
// if (ch_dict_bench[curr]) {
// ch_dict_bench[curr].push([prev, distance]);
// } else {
// ch_dict_bench[curr] = [[prev, distance]];
// }
// if (ch_dict_bench[prev]) {
// ch_dict_bench[prev].push([curr, distance]);
// } else {
// ch_dict_bench[prev] = [[curr, distance]];
// }
// }
let prev = '';
let distance = 0;
for (let i = 0; i < n; ++i) {
const curr = l[i];
if (i) distance += haversine_distance(nodes[curr], nodes[l[i-1]]);
// 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, distance]);
} else {
ch_dict[curr] = [[prev, distance]];
}
if (ch_dict[prev]) {
ch_dict[prev].push([curr, distance]);
} else {
ch_dict[prev] = [[curr, distance]];
}
}
prev = curr;
distance = 0;
}
}
}
// 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...");
// // TODO: delete this
// const seq = __spa(nodes, clean_nodes, ways, location, ch_dict, ch_dict_bench, count, aff, actual_start_node_id, actual_end_node_id);
const seq = __spa(clean_nodes, ways, location, 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]);
});
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;
if (haversine_distance([minlat, minlon], [maxlat, maxlon]) > 100) {
res.status(500);
sill('rejected');
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);
let ps = {};
let ws = {};
debug_response.elements.forEach((it) => {
if (it.type === "node") {
ps[it.id] = [it.lat, it.lon];
} else if (it.type === "way") {
ws[it.id] = it.nodes;
}
});
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 => {
console.debug(e);
res.status(500);
});
}

9
api/handshake.js Normal file
View File

@ -0,0 +1,9 @@
import {__APP_VERSION__, __APP_INTRO__} from "../PublicProperty";
export default function handler(req,res){
res.status(200).json({
log: 'Method: handshake\nStatus: responded handshake request',
version: __APP_VERSION__,
intro: __APP_INTRO__,
});
}

11667
example/interpreter.json Normal file

File diff suppressed because it is too large Load Diff

BIN
misc/icon.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

218
package-lock.json generated
View File

@ -1,13 +1,14 @@
{
"name": "nanomap",
"version": "0.1.0",
"version": "0.1.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "nanomap",
"version": "0.1.0",
"version": "0.1.1",
"dependencies": {
"@datastructures-js/priority-queue": "^6.3.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.14.18",
@ -18,6 +19,12 @@
"react-dom": "^18.2.0",
"react-leaflet": "^4.2.1",
"react-scripts": "5.0.1"
},
"devDependencies": {
"@babel/cli": "^7.23.4",
"@babel/core": "^7.23.5",
"@babel/plugin-proposal-private-property-in-object": "latest",
"@babel/preset-flow": "^7.23.3"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@ -51,12 +58,90 @@
"node": ">=6.0.0"
}
},
"node_modules/@babel/code-frame": {
"version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
"node_modules/@babel/cli": {
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.23.4.tgz",
"integrity": "sha512-j3luA9xGKCXVyCa5R7lJvOMM+Kc2JEnAEIgz2ggtjQ/j5YUVgfsg/WsG95bbsgq7YLHuiCOzMnoSasuY16qiCw==",
"dev": true,
"dependencies": {
"@babel/highlight": "^7.22.13",
"@jridgewell/trace-mapping": "^0.3.17",
"commander": "^4.0.1",
"convert-source-map": "^2.0.0",
"fs-readdir-recursive": "^1.1.0",
"glob": "^7.2.0",
"make-dir": "^2.1.0",
"slash": "^2.0.0"
},
"bin": {
"babel": "bin/babel.js",
"babel-external-helpers": "bin/babel-external-helpers.js"
},
"engines": {
"node": ">=6.9.0"
},
"optionalDependencies": {
"@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3",
"chokidar": "^3.4.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/cli/node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true,
"engines": {
"node": ">= 6"
}
},
"node_modules/@babel/cli/node_modules/make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
"dev": true,
"dependencies": {
"pify": "^4.0.1",
"semver": "^5.6.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@babel/cli/node_modules/pify": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/@babel/cli/node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true,
"bin": {
"semver": "bin/semver"
}
},
"node_modules/@babel/cli/node_modules/slash": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/@babel/code-frame": {
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
"integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
"dependencies": {
"@babel/highlight": "^7.23.4",
"chalk": "^2.4.2"
},
"engines": {
@ -72,20 +157,20 @@
}
},
"node_modules/@babel/core": {
"version": "7.23.3",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz",
"integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==",
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.5.tgz",
"integrity": "sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.22.13",
"@babel/generator": "^7.23.3",
"@babel/code-frame": "^7.23.5",
"@babel/generator": "^7.23.5",
"@babel/helper-compilation-targets": "^7.22.15",
"@babel/helper-module-transforms": "^7.23.3",
"@babel/helpers": "^7.23.2",
"@babel/parser": "^7.23.3",
"@babel/helpers": "^7.23.5",
"@babel/parser": "^7.23.5",
"@babel/template": "^7.22.15",
"@babel/traverse": "^7.23.3",
"@babel/types": "^7.23.3",
"@babel/traverse": "^7.23.5",
"@babel/types": "^7.23.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@ -142,11 +227,11 @@
}
},
"node_modules/@babel/generator": {
"version": "7.23.3",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz",
"integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==",
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz",
"integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==",
"dependencies": {
"@babel/types": "^7.23.3",
"@babel/types": "^7.23.5",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
@ -425,9 +510,9 @@
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
"integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
"engines": {
"node": ">=6.9.0"
}
@ -462,22 +547,22 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz",
"integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==",
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.5.tgz",
"integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==",
"dependencies": {
"@babel/template": "^7.22.15",
"@babel/traverse": "^7.23.2",
"@babel/types": "^7.23.0"
"@babel/traverse": "^7.23.5",
"@babel/types": "^7.23.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
"integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
"dependencies": {
"@babel/helper-validator-identifier": "^7.22.20",
"chalk": "^2.4.2",
@ -488,9 +573,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.23.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz",
"integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==",
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz",
"integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==",
"bin": {
"parser": "bin/babel-parser.js"
},
@ -1896,6 +1981,23 @@
"semver": "bin/semver.js"
}
},
"node_modules/@babel/preset-flow": {
"version": "7.23.3",
"resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.23.3.tgz",
"integrity": "sha512-7yn6hl8RIv+KNk6iIrGZ+D06VhVY35wLVf23Cz/mMu1zOr7u4MMP4j0nZ9tLf8+4ZFpnib8cFYgB/oYg9hfswA==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/helper-validator-option": "^7.22.15",
"@babel/plugin-transform-flow-strip-types": "^7.23.3"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/preset-modules": {
"version": "0.1.6-no-external-plugins",
"resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz",
@ -1976,18 +2078,18 @@
}
},
"node_modules/@babel/traverse": {
"version": "7.23.3",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz",
"integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==",
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.5.tgz",
"integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==",
"dependencies": {
"@babel/code-frame": "^7.22.13",
"@babel/generator": "^7.23.3",
"@babel/code-frame": "^7.23.5",
"@babel/generator": "^7.23.5",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.23.3",
"@babel/types": "^7.23.3",
"@babel/parser": "^7.23.5",
"@babel/types": "^7.23.5",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
@ -1996,11 +2098,11 @@
}
},
"node_modules/@babel/types": {
"version": "7.23.3",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz",
"integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==",
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz",
"integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==",
"dependencies": {
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-string-parser": "^7.23.4",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
},
@ -2283,6 +2385,19 @@
"postcss-selector-parser": "^6.0.10"
}
},
"node_modules/@datastructures-js/heap": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/@datastructures-js/heap/-/heap-4.3.2.tgz",
"integrity": "sha512-7/9QSsIZ+wMG3C9++mz9iOjdtTp9C036PISHvNjG3eyFO8nXOBJQFKgeV7M6/+EPl+oXXFRGBb8Ue60LsqTqGw=="
},
"node_modules/@datastructures-js/priority-queue": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@datastructures-js/priority-queue/-/priority-queue-6.3.0.tgz",
"integrity": "sha512-bJkryPys8zVYMCSjyBPYzJlw5V2kMeOYzGHRBXiGkwqTv8vZ/Ux5RO736T8Y6l3cH+ocbQV9UxlsFwA9qI4VhA==",
"dependencies": {
"@datastructures-js/heap": "^4.3.1"
}
},
"node_modules/@emotion/babel-plugin": {
"version": "11.11.0",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
@ -3617,6 +3732,13 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
},
"node_modules/@nicolo-ribaudo/chokidar-2": {
"version": "2.1.8-no-fsevents.3",
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz",
"integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==",
"dev": true,
"optional": true
},
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
"version": "5.1.1-v1",
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
@ -8477,6 +8599,12 @@
"resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz",
"integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew=="
},
"node_modules/fs-readdir-recursive": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
"integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==",
"dev": true
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",

View File

@ -1,8 +1,9 @@
{
"name": "nanomap",
"version": "0.1.0",
"version": "0.1.4a",
"private": true,
"dependencies": {
"@datastructures-js/priority-queue": "^6.3.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.14.18",
@ -14,6 +15,12 @@
"react-leaflet": "^4.2.1",
"react-scripts": "5.0.1"
},
"devDependencies": {
"@babel/cli": "^7.23.4",
"@babel/core": "^7.23.5",
"@babel/plugin-proposal-private-property-in-object": "latest",
"@babel/preset-flow": "^7.23.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",

BIN
public/icon.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -6,7 +6,7 @@
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossorigin=""/>
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<link rel="shortcut icon" href="%PUBLIC_URL%/icon.jpeg">
<!--
Notice the use of %PUBLIC_URL% in the tag above.
It will be replaced with the URL of the `public` folder during the build.
@ -16,7 +16,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<title>nanoMap</title>
</head>
<body>
<div id="root"></div>

View File

@ -1,18 +1,126 @@
import React, { Component } from 'react';
import logo from './logo.svg';
import React, {Component, useState} from 'react';
import {Transition} from 'react-transition-group';
import './App.css';
import UMap from "./UMap";
import {
Alert,
Box, Button,
CircularProgress,
DialogContent,
DialogTitle,
Modal, ModalClose,
ModalDialog,
Sheet,
Typography
} from "@mui/joy";
import {WebhookOutlined} from "@mui/icons-material";
import {post} from "./Networking";
class App extends Component {
render() {
export default function App() {
const [featureOpen, setFeatureOpen] = useState(true);
const [version, setVersion] = useState('');
const [intro, setIntro] = useState('');
const hs = setInterval(() => {
post('GET','handshake', []).then((response) => {
const {version, intro} = response;
setVersion(version);
setIntro(intro);
clearInterval(hs);
}).catch((e) => {
console.error(e);
setVersion('');
});
}, 2000);
return (
<div className="App">
<div className="App" style={{pointerEvents: version !== '' ? 'all' : 'none'}}>
<div style={{display: 'flex', justifyContent: 'center', alignItems: 'center'}}>
<UMap/>
<Transition in={featureOpen} timeout={400}>
{(state) => (
<Modal style={{pointerEvents: version !== '' ? 'all' : 'none'}}
keepMounted
open={!['exited', 'exiting'].includes(state)}
onClose={() => setFeatureOpen(false)}
slotProps={{
backdrop: {
sx: {
opacity: 0,
backdropFilter: 'none',
transition: `opacity 400ms, backdrop-filter 400ms`,
...{
entering: {opacity: 1, backdropFilter: 'blur(8px)'},
entered: {opacity: 1, backdropFilter: 'blur(8px)'},
}[state],
},
},
}}
sx={{
visibility: state === 'exited' ? 'hidden' : 'visible',
zIndex: 'tooltip'
}}
>
<ModalDialog
sx={{
opacity: 0,
transition: `opacity 300ms`,
...{
entering: {opacity: 1},
entered: {opacity: 1},
}[state],
display: 'flex',
width: version !== '' ? '60%' : 'auto',
height: 'auto',
maxWidth: '50vw',
}}
>
<Sheet sx={{display: 'flex',marginTop: version!==''?'5%':0, flexDirection: 'column'}}>
<Alert style={{display: version !== '' ? 'none' : 'flex'}}
variant="soft"
color="warning"
invertedColors
startDecorator={
<CircularProgress size="lg" color="warning">
<WebhookOutlined/>
</CircularProgress>
}
sx={{alignItems: 'flex-start', gap: '1rem'}}
>
<Box sx={{flex: 1}}>
<Typography level="title-lg">Still connecting...</Typography>
<Typography level="body-md">
nanoMap requires a valid backend to work. If it takes too long, please
check
your connection or see our status page.
</Typography>
</Box>
</Alert>
<Sheet style={{display: version !== '' ? 'flex' : 'none'}}>
<img src={'icon.jpeg'}
style={{
height: '150px',
display: 'flex',
margin: 'auto',
borderRadius: '50%'
}}
alt={'nanoMap'}/>
</Sheet>
<DialogTitle style={{display: version !== '' ? 'flex' : 'none'}}
sx={{margin: 'auto', fontSize: '200%', textAlign: 'center'}}>What's new
in
nanoMap {version}</DialogTitle>
<DialogContent style={{display: version !== '' ? 'flex' : 'none'}}
sx={{margin: 'auto', maxWidth: '80%'}}>
<div dangerouslySetInnerHTML={{__html: intro}}/>
</DialogContent>
</Sheet>
<Sheet onClick={()=>setFeatureOpen(false)} sx={{display: version!==''?'flex':'none', bottom: '10%', width: '100%'}}>
<Button size="lg" sx={{width: '50%', maxWidth: '20vw', display: 'flex', margin: 'auto'}}>Done</Button>
</Sheet>
</ModalDialog>
</Modal>
)}
</Transition>
</div>
</div>
);
}
}
export default App;

View File

@ -1,16 +1,22 @@
export function post(method,args) {
const data = new FormData();
data.append('method', method)
data.append('payload', args.join(','));
let res = '';
import {sill} from "./tools/Debug";
export function post(type,method,args) {
let ft = async () => {
const response = await fetch('http://127.0.0.1:825/', {
method: 'POST',
body: data
let response;
if(type==='GET'){
const payload=args.join('@@');
response = await fetch(`/api/${method}${payload.length?"?query=":""}${payload}`, {
method: 'GET',
});
const response_data = await response.text();
console.log(`Received: ${response_data}`);
}else if(type==='POST'){
response = await fetch(`/api/${method}`, {
method: 'POST',
body: JSON.stringify(args),
});
}
const response_data = await response.json();
sill(`Request "${method}" finished:\n ${response_data.log.replaceAll('\n','\n ')}`);
return response_data;
};
return ft();
};
}

View File

@ -1,12 +1,13 @@
import {Button, IconButton, ButtonGroup, Menu, MenuItem, Stack} from "@mui/joy";
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import React from "react";
import {Delete, LocationOn} from "@mui/icons-material";
export default function SimulateClick() {
export default function SimulateClick({isLocated,isMarkersEmpty,relocator,clearMarkers}) {
return (
<ButtonGroup buttonFlex={1} aria-label="flex button group" sx={{zIndex: 'modal'}}>
<Button variant={'solid'} color={'success'}>Starting</Button>
<Button variant={'solid'} color={'primary'}>Destination</Button>
<Button disabled={isLocated} variant={'solid'} color={'warning'} startDecorator={<LocationOn />} onClick={relocator}>Relocate</Button>
<Button disabled={isMarkersEmpty} variant={'solid'} color={'danger'} startDecorator={<Delete />} onClick={clearMarkers}>Clear</Button>
</ButtonGroup>
);
}

View File

@ -1,34 +1,48 @@
import React, {Component, useEffect, useRef, useState} from 'react';
import SearchIcon from '@mui/icons-material/Search';
import {MapContainer, TileLayer, Marker, Popup, useMap} from 'react-leaflet';
import {MapContainer, TileLayer, Marker, useMap, Polyline} from 'react-leaflet';
import {useMapEvents} from 'react-leaflet/hooks';
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 AppleParkLoc=[37.334835049999995,-122.01139165956805];
const defaultZoomLevel=16;
const defaultLocationName = 'Apple Park';
const defaultLocation = [37.334835049999995, -122.01139165956805];
class Markers extends Component {
// A location is a [lat,lon] pair.
constructor(props) {
super(props);
this.state = {markers: [], candMarkers: []};
this.state = {markers: [], candMarkers: [], mksEmpty: true, candEmpty: true, polylines: []};
this.clearMarkers = this.clearMarkers.bind(this);
this.clearCandMarkers = this.clearCandMarkers.bind(this);
}
render() {
const fillBlueOptions = {fillColor: 'blue'};
const pl = this.state.polylines;
const mks = this.state.markers.map((p, i) => (
<Marker position={[p[0], p[1]]} opacity={1.0}><Popup>Generated
by <code>Markers()</code></Popup></Marker>
<Marker key={`m` + i} interactive={false} position={[p[0], p[1]]} opacity={1.0}/>
));
const cmks = this.state.candMarkers.map((p, i) => (
<Marker interactive={false} position={[p[0], p[1]]} opacity={0.5}><Popup>Generated
by <code>Markers()</code></Popup></Marker>
<Marker key={`c` + i} interactive={false} position={[p[0], p[1]]} opacity={0.5}/>
));
return mks.concat(cmks);
return [
...mks.concat(cmks),
pl && pl.length > 1 ? <Polyline pathOptions={fillBlueOptions} positions={pl}/> : <div/>
];
}
addMarker(lat, lng) {
this.setState((prev) => ({
markers: [...prev.markers, [lat, lng]], candMarkers: prev.candMarkers,
markers: [...prev.markers, [lat, lng]],
candMarkers: prev.candMarkers,
mksEmpty: false,
candEmpty: prev.candEmpty,
polylines: prev.polylines,
}));
this.getFocus();
}
@ -37,41 +51,85 @@ class Markers extends Component {
this.setState((prev) => ({
candMarkers: [...prev.candMarkers, [lat, lng]],
markers: prev.markers,
mksEmpty: prev.mksEmpty,
candEmpty: false,
polylines: prev.polylines,
}));
this.getFocus(true);
}
clearMarkers() {
this.setState((prev) => ({
markers: [],
candMarkers: prev.candMarkers,
mksEmpty: true,
candEmpty: prev.candEmpty,
polylines: [],
}));
this.getFocus();
}
clearMarkers() {
this.setState((prev) => ({markers: [], candMarkers: prev.candMarkers}));
this.getFocus();
}
clearCandMarkers() {
this.setState((prev) => ({markers: prev.markers, candMarkers: []}));
this.setState((prev) => ({
markers: prev.markers,
candMarkers: [],
mksEmpty: prev.mksEmpty,
candEmpty: true,
polylines: prev.polylines,
}));
this.getFocus();
}
getFocus() {
getFocus(virtual=false) {
let currentFocus = [];
if (this.state.candMarkers.length) {
currentFocus = this.state.candMarkers.at(-1);
} else if (this.state.markers.length) {
currentFocus = this.state.markers.at(-1);
} else {
currentFocus=AppleParkLoc;
currentFocus = defaultLocation;
}
this.props.focusUpdater(currentFocus);
if(virtual){
this.props.locator(currentFocus);
}
}
function MapClickHandler({mks}) {
flushPolylines(pl, clear=true) {
this.setState((prev) => ({
markers: prev.markers,
candMarkers: prev.candMarkers,
mksEmpty: prev.mksEmpty,
candEmpty: prev.candEmpty,
polylines: clear ? pl : [...pl, ...prev.polylines], // mind the ordering
}));
// TODO
}
}
function MapClickHandler({mks,focusUpdater,locator,locker}) {
const map = useMapEvents({
click: (e) => {
map.locate();
const lat = e.latlng.lat, lng = e.latlng.lng;
console.info(`Clicking on ${lat} ${lng}`);
const {lat,lng}=e.latlng;
sill(`Clicking on ${lat} ${lng}`);
mks.current.addMarker(lat, lng);
post('click', [lat, lng]);
post('POST', 'click', mks.current.state.markers.slice(-2)).then((response) => {
// TODO: real functionality
const pl = JSON.parse(response.multipolyline);
// DEBUG
// response.__debug_pts.forEach(({lat,lon})=>{
// mks.current.addCandMarker(lat,lon);
// });
sill(`pl = ${JSON.stringify(pl)}`);
if (pl.length > 1) mks.current.flushPolylines(pl, false);
focusUpdater([lat,lng]);
locator([lat,lng]);
locker(true);
}).catch((e) => {
console.error(e);
// location.reload();
});
},
// TODO
locationfound: (location) => {
@ -81,11 +139,21 @@ function MapClickHandler({mks}) {
return null;
}
const LocationSearch = ({mks}) => {
const LocationSearch = ({mks, focus, nb}) => {
const [query, setQuery] = useState('');
const [loading, setLoading] = useState(false);
const [suggestedLocations, setSuggestedLocations] = useState([]);
const [controller, setController] = useState(new AbortController());
const [everywhere,setEverywhere]=useState(false);
const SearchLogo=()=>{
const cf=()=>setEverywhere(!everywhere);
if(everywhere){
return <SearchIcon onClick={cf}/>;
} else{
return <NearMe onClick={cf}/>;
}
};
useEffect(() => {
return () => controller.abort();
@ -103,7 +171,7 @@ const LocationSearch = ({mks}) => {
return;
}
const response = await fetch(
`https://nominatim.openstreetmap.org/search?format=json&q=${v}`,
`https://nominatim.openstreetmap.org/search?format=json&q=${v}${everywhere?'':' '+focus[0]+','+focus[1]}`,
{signal: newController.signal}
);
if (response.ok) {
@ -130,9 +198,11 @@ const LocationSearch = ({mks}) => {
}
};
return <Autocomplete sx={{zIndex: 'snackbar'}} loading={loading} loadingText={"Searching..."} startDecorator={<SearchIcon/>}
placeholder={'Find a location...'} onInputChange={handleSearch}
options={suggestedLocations} isOptionEqualToValue={(option, value) => option.value === value.value} endDecorator={
return <Autocomplete sx={{zIndex: 'snackbar'}} loading={loading} loadingText={"Searching..."}
startDecorator={<SearchLogo/>}
placeholder={everywhere?'Search everywhere...':`Search near${nb}`} onInputChange={handleSearch}
options={suggestedLocations}
isOptionEqualToValue={(option, value) => option.value === value.value} endDecorator={
loading ? (
<CircularProgress size="sm" sx={{bgcolor: 'background.surface'}}/>
) : null
@ -146,9 +216,35 @@ function ChangeView({center,zoom}){
export default function UMap() {
const markersRef = useRef(null);
const [focus, setFocus]=useState([37.334835049999995,-122.01139165956805]);
const zoom=13;
const sf=(a)=>{setFocus(a);console.log(`triggered focus update, new focus is ${focus}`);};
const [focus, setFocus] = useState(defaultLocation);
const [located,setLocated]=useState(true);
const [locatedFocus,setLocatedFocus]=useState(defaultLocation);
const [nearbyName, setNearbyName] = useState(' ' + defaultLocationName);
const [zoom,setZoom] = useState(defaultZoomLevel);
const sf = (a) => {
setFocus(a);
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'));
};
const relo=()=> {
sf(locatedFocus);
setLocated(true);
};
const ViewportChange = () => {
const map = useMapEvents({
dragend: (e) => {
const center = e.target.getCenter();
sf([center.lat, center.lng]);
setLocated(false);
},
zoomend: (e)=>{
setZoom(e.target.getZoom());
sf(focus);
}
});
return null;
};
return (
<Sheet>
<MapContainer style={{height: '100vh', width: '100vw',}} sx={{zIndex: 'fab'}} center={focus} zoom={zoom}
@ -158,12 +254,13 @@ export default function UMap() {
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Markers ref={markersRef} focusUpdater={sf}/>
<MapClickHandler mks={markersRef}/>
<Markers ref={markersRef} focusUpdater={sf} locator={setLocatedFocus} />
<MapClickHandler mks={markersRef} focusUpdater={setFocus} locator={setLocatedFocus} locker={setLocated}/>
<ViewportChange/>
</MapContainer>
<Sheet sx={{position: 'absolute', top: '20px', left: '10vw', zIndex: 'modal'}}>
<SimulateClick />
<LocationSearch mks={markersRef}/>
<Sheet sx={{position: 'absolute', top: '20px', right: '10vw', zIndex: 'modal'}}>
<SimulateClick isLocated={located} relocator={relo} isMarkersEmpty={markersRef.current ? markersRef.current.state.mksEmpty : true} clearMarkers={markersRef.current ? markersRef.current.clearMarkers : null}/>
<LocationSearch nb={nearbyName} mks={markersRef} focus={focus}/>
</Sheet>
</Sheet>
);

1
src/tools Symbolic link
View File

@ -0,0 +1 @@
../tools

2
test/test_nearest.js Normal file
View File

@ -0,0 +1,2 @@
import shortestPath from "../comp/ShortestPath.js";
console.log('Hello!');

28
tools/Debug.js Normal file
View File

@ -0,0 +1,28 @@
// 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));
});

14
tools/Misc.js Normal file
View File

@ -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;
}
}
});

31
tools/PathBench.js Normal file
View File

@ -0,0 +1,31 @@
import * as SP from "./ShortestPath";
import {sill} from "./Debug";
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, _;
//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();
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(`==============================`);
return res;
}

250
tools/ShortestPath.js Normal file
View File

@ -0,0 +1,250 @@
import {MinPriorityQueue} from "@datastructures-js/priority-queue";
import {sill, noexcept, sill_unwrap} from "./Debug";
import {find_common} from "./Misc";
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 ways unused in this implementation
* @param loc 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, ways, loc, ch, count, aff, u, p) {
// sill(`node count: ${Object.keys(nodes).length}`);
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;
if (v === p) {
break;
}
vis.add(v);
const t = ch[v].length;
for (let j = 0; j < t; ++j) {
const [c, w] = 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;
}
/**
* Use Dijkstra 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
*/
function __obvious_dijkstra(nodes, ways, loc, ch, count, aff, u, p) {
// sill(`node count: ${Object.keys(nodes).length}`);
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);
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], 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;
}
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;
}
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;
}
export const haversine_distance = noexcept(__haversine_distance);
export const dijkstra = noexcept(__dijkstra);
export const obvious_dijkstra = noexcept(__obvious_dijkstra);
export const obvious_a_star = noexcept(__obvious_a_star);