Use a web worker to calculate, to avoid locking up the browser. Also, use semicolons between points, and fix bug where it is impossible to calculate route when receiving points in the URL

This commit is contained in:
Martin Asprusten
2026-08-29 13:43:19 +02:00
parent eac2a41f18
commit f3a1f058c0
4 changed files with 72 additions and 28 deletions
+29 -24
View File
@@ -1,5 +1,4 @@
import MapHandler from './map/maphandler'; import MapHandler from './map/maphandler';
import Module, { type MainModule } from '../native/salesman.js';
import haversine from 'haversine-distance'; import haversine from 'haversine-distance';
import './style.css'; import './style.css';
import { LatLng } from 'leaflet'; import { LatLng } from 'leaflet';
@@ -8,12 +7,11 @@ import { createUrl, parseUrl } from './url/UrlHandler.js';
import { wktToGeoJSON } from '@terraformer/wkt'; import { wktToGeoJSON } from '@terraformer/wkt';
import PPENHandler from './files/ppenhandler.js'; import PPENHandler from './files/ppenhandler.js';
import type { GeoJsonObject } from 'geojson'; import type { GeoJsonObject } from 'geojson';
import type { SearchStatusMessage, StartSearchMessage } from './worker/messages.js';
// Outside influences // Outside influences
let mapHandler = new MapHandler(); let mapHandler = new MapHandler();
let module: MainModule = await Module(); let worker = new Worker(new URL('./worker/worker.ts', import.meta.url), {type: 'module'});
// HTTP elements to be edited // HTTP elements to be edited
let clearMapButton = document.getElementById('clear-map'); let clearMapButton = document.getElementById('clear-map');
@@ -57,7 +55,7 @@ mapHandler.addMarkerClickedListener((latLng) => {
awaiting(latLng); awaiting(latLng);
}); });
awaitingResolutions = []; awaitingResolutions = [];
}) });
@@ -65,6 +63,7 @@ mapHandler.addMarkerClickedListener((latLng) => {
interface State { interface State {
points: LatLng[] | null, points: LatLng[] | null,
distances: [LatLng, LatLng, number][], distances: [LatLng, LatLng, number][],
calculating: boolean,
foundPath: number[] | null, foundPath: number[] | null,
complexPath: GeoJsonObject | null, complexPath: GeoJsonObject | null,
@@ -81,7 +80,8 @@ interface State {
let state: State = { let state: State = {
points: null, points: null,
distances: [], distances: [],
foundPath: [], calculating: false,
foundPath: null,
complexPath: null, complexPath: null,
ppenHandler: null, ppenHandler: null,
@@ -122,7 +122,8 @@ function updateState() {
// Always just allow the share link button, do nothing here // Always just allow the share link button, do nothing here
// Only allow the calculate route button when we have more than three points, and don't already have a route // Only allow the calculate route button when we have more than three points, and don't already have a route
if (state.points == null || state.points.length < 3 || state.foundPath != null) { // Also, disallow the get route button when currently calculating
if (state.points == null || state.points.length < 3 || state.foundPath != null || state.calculating) {
allowCalculateRoute = false; allowCalculateRoute = false;
} }
@@ -283,16 +284,24 @@ function updateState() {
} }
} }
function calculateRoute(): number[] { // In case of messages from the web worker, do work
worker.onmessage = e => {
let message = e.data as SearchStatusMessage;
state.foundPath = message.path;
state.calculating = false;
updateState();
}
function calculateRoute(): void {
if (!state.points) { if (!state.points) {
return []; return;
} }
let points: LatLng[] = state.points; let points: LatLng[] = state.points;
let weights = new module.Weights(); let weights: number[][] = [];
for (var i = 0; i < points.length; i++) { for (var i = 0; i < points.length; i++) {
let weightsRow = new module.WeightsRow(); let weightsRow: number[] = [];
for (var j = 0; j < points.length; j++) { for (var j = 0; j < points.length; j++) {
var distance: number; var distance: number;
if (i == j) { if (i == j) {
@@ -312,20 +321,16 @@ function calculateRoute(): number[] {
} }
} }
weightsRow.push_back(distance); weightsRow.push(distance);
} }
weights.push_back(weightsRow); weights.push(weightsRow);
} }
let path = module.findShortestPath(weights); let startSearchMessage: StartSearchMessage = {
var foundPath = []; weights: weights
for (var i = 0; i < path.size(); i++) { };
let pointIndex = path.get(i);
if (typeof pointIndex != 'undefined') { worker.postMessage(startSearchMessage);
foundPath.push(pointIndex);
}
}
return foundPath;
} }
function populateDistancesTable() { function populateDistancesTable() {
@@ -788,9 +793,9 @@ shareLinkButton?.addEventListener('click', async _ => {
calculateRouteButton?.addEventListener('click', _ => { calculateRouteButton?.addEventListener('click', _ => {
if (calculateRouteButton.classList.contains('clickable')) { if (calculateRouteButton.classList.contains('clickable')) {
let path = calculateRoute(); state.calculating = true;
state.foundPath = path;
updateState(); updateState();
calculateRoute();
} }
}); });
+4 -4
View File
@@ -11,7 +11,7 @@ export function parseUrl(url: string): [LatLng[] | null, [LatLng, LatLng, number
points = []; points = [];
let pointsString = parsedUrl.searchParams.get('points'); let pointsString = parsedUrl.searchParams.get('points');
pointsString?.split('-').forEach(pointString => { pointsString?.split(';').forEach(pointString => {
let splitString = pointString.split(','); let splitString = pointString.split(',');
if (splitString && splitString.length == 2) { if (splitString && splitString.length == 2) {
let lat = parseFloat(splitString[0]); let lat = parseFloat(splitString[0]);
@@ -32,7 +32,7 @@ export function parseUrl(url: string): [LatLng[] | null, [LatLng, LatLng, number
distances = []; distances = [];
let distancesString = parsedUrl.searchParams.get('distances'); let distancesString = parsedUrl.searchParams.get('distances');
distancesString?.split('-').forEach(distanceString => { distancesString?.split(';').forEach(distanceString => {
let splitString = distanceString.split(','); let splitString = distanceString.split(',');
if (splitString && splitString.length == 3) { if (splitString && splitString.length == 3) {
let from = parseInt(splitString[0]); let from = parseInt(splitString[0]);
@@ -67,7 +67,7 @@ export function createUrl(clickedPoints: LatLng[] | null, definedDistances: [Lat
if(clickedPoints) { if(clickedPoints) {
let pointStrings = clickedPoints.map(latLng => latLng.lat.toFixed(6) + ',' + latLng.lng.toFixed(6)); let pointStrings = clickedPoints.map(latLng => latLng.lat.toFixed(6) + ',' + latLng.lng.toFixed(6));
if (pointStrings.length > 0) { if (pointStrings.length > 0) {
urlStrings.push('points=' + pointStrings.join('-')); urlStrings.push('points=' + pointStrings.join(';'));
} }
} }
@@ -90,7 +90,7 @@ export function createUrl(clickedPoints: LatLng[] | null, definedDistances: [Lat
} }
if (distancesStrings.length > 0) { if (distancesStrings.length > 0) {
urlStrings.push('distances=' + distancesStrings.join('-')); urlStrings.push('distances=' + distancesStrings.join(';'));
} }
} }
+7
View File
@@ -0,0 +1,7 @@
export interface StartSearchMessage {
weights: number[][];
}
export interface SearchStatusMessage {
path: number[];
}
+32
View File
@@ -0,0 +1,32 @@
import Module, { type MainModule } from '../../native/salesman.js';
import type { SearchStatusMessage, StartSearchMessage } from "./messages"
let modulePromise: Promise<MainModule> = Module();
onmessage = async (e) => {
let module = await modulePromise;
let message = e.data as StartSearchMessage;
let nativeWeights = new module.Weights;
message.weights.forEach(weightsRow => {
let nativeWeightsRow = new module.WeightsRow();
weightsRow.forEach(weight => {
nativeWeightsRow.push_back(weight);
});
nativeWeights.push_back(nativeWeightsRow);
});
let path = module.findShortestPath(nativeWeights);
let foundPath = [];
for (var i = 0; i < path.size(); i++) {
let pointIndex = path.get(i);
if (typeof pointIndex != 'undefined') {
foundPath.push(pointIndex);
}
};
let finishedMessage: SearchStatusMessage = {
path: foundPath
};
postMessage(finishedMessage);
}