From f3a1f058c04b9e86b10a5b617426491cf375e3f5 Mon Sep 17 00:00:00 2001 From: Martin Asprusten Date: Sat, 29 Aug 2026 13:43:19 +0200 Subject: [PATCH] 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 --- src/main.ts | 53 +++++++++++++++++++++++------------------- src/url/UrlHandler.ts | 8 +++---- src/worker/messages.ts | 7 ++++++ src/worker/worker.ts | 32 +++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 28 deletions(-) create mode 100644 src/worker/messages.ts create mode 100644 src/worker/worker.ts diff --git a/src/main.ts b/src/main.ts index cdc4a86..07c40db 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,4 @@ import MapHandler from './map/maphandler'; -import Module, { type MainModule } from '../native/salesman.js'; import haversine from 'haversine-distance'; import './style.css'; import { LatLng } from 'leaflet'; @@ -8,12 +7,11 @@ import { createUrl, parseUrl } from './url/UrlHandler.js'; import { wktToGeoJSON } from '@terraformer/wkt'; import PPENHandler from './files/ppenhandler.js'; import type { GeoJsonObject } from 'geojson'; +import type { SearchStatusMessage, StartSearchMessage } from './worker/messages.js'; // Outside influences 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 let clearMapButton = document.getElementById('clear-map'); @@ -57,7 +55,7 @@ mapHandler.addMarkerClickedListener((latLng) => { awaiting(latLng); }); awaitingResolutions = []; -}) +}); @@ -65,6 +63,7 @@ mapHandler.addMarkerClickedListener((latLng) => { interface State { points: LatLng[] | null, distances: [LatLng, LatLng, number][], + calculating: boolean, foundPath: number[] | null, complexPath: GeoJsonObject | null, @@ -81,7 +80,8 @@ interface State { let state: State = { points: null, distances: [], - foundPath: [], + calculating: false, + foundPath: null, complexPath: null, ppenHandler: null, @@ -122,7 +122,8 @@ function updateState() { // 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 - 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; } @@ -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) { - return []; + return; } let points: LatLng[] = state.points; - let weights = new module.Weights(); + let weights: number[][] = []; for (var i = 0; i < points.length; i++) { - let weightsRow = new module.WeightsRow(); + let weightsRow: number[] = []; for (var j = 0; j < points.length; j++) { var distance: number; 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); - var foundPath = []; - for (var i = 0; i < path.size(); i++) { - let pointIndex = path.get(i); - if (typeof pointIndex != 'undefined') { - foundPath.push(pointIndex); - } - } - return foundPath; + let startSearchMessage: StartSearchMessage = { + weights: weights + }; + + worker.postMessage(startSearchMessage); } function populateDistancesTable() { @@ -788,9 +793,9 @@ shareLinkButton?.addEventListener('click', async _ => { calculateRouteButton?.addEventListener('click', _ => { if (calculateRouteButton.classList.contains('clickable')) { - let path = calculateRoute(); - state.foundPath = path; + state.calculating = true; updateState(); + calculateRoute(); } }); diff --git a/src/url/UrlHandler.ts b/src/url/UrlHandler.ts index 1775bee..fc71a12 100644 --- a/src/url/UrlHandler.ts +++ b/src/url/UrlHandler.ts @@ -11,7 +11,7 @@ export function parseUrl(url: string): [LatLng[] | null, [LatLng, LatLng, number points = []; let pointsString = parsedUrl.searchParams.get('points'); - pointsString?.split('-').forEach(pointString => { + pointsString?.split(';').forEach(pointString => { let splitString = pointString.split(','); if (splitString && splitString.length == 2) { let lat = parseFloat(splitString[0]); @@ -32,7 +32,7 @@ export function parseUrl(url: string): [LatLng[] | null, [LatLng, LatLng, number distances = []; let distancesString = parsedUrl.searchParams.get('distances'); - distancesString?.split('-').forEach(distanceString => { + distancesString?.split(';').forEach(distanceString => { let splitString = distanceString.split(','); if (splitString && splitString.length == 3) { let from = parseInt(splitString[0]); @@ -67,7 +67,7 @@ export function createUrl(clickedPoints: LatLng[] | null, definedDistances: [Lat if(clickedPoints) { let pointStrings = clickedPoints.map(latLng => latLng.lat.toFixed(6) + ',' + latLng.lng.toFixed(6)); 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) { - urlStrings.push('distances=' + distancesStrings.join('-')); + urlStrings.push('distances=' + distancesStrings.join(';')); } } diff --git a/src/worker/messages.ts b/src/worker/messages.ts new file mode 100644 index 0000000..848aa3b --- /dev/null +++ b/src/worker/messages.ts @@ -0,0 +1,7 @@ +export interface StartSearchMessage { + weights: number[][]; +} + +export interface SearchStatusMessage { + path: number[]; +} \ No newline at end of file diff --git a/src/worker/worker.ts b/src/worker/worker.ts new file mode 100644 index 0000000..d2aa629 --- /dev/null +++ b/src/worker/worker.ts @@ -0,0 +1,32 @@ +import Module, { type MainModule } from '../../native/salesman.js'; +import type { SearchStatusMessage, StartSearchMessage } from "./messages" + +let modulePromise: Promise = 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); +} \ No newline at end of file