GPS Route Optimizer
Fixed-start, fixed-end route optimization using the uploaded real road-distance matrix with LKH-style variable-depth k-opt search, backtracking, 1-tree α-nearness candidates, randomized kicks, multiple runs, and exhaustive final 2-opt polishing.
Optimized Route
| # | Latitude | Longitude |
|---|
<!– Paste this complete block into an Elementor HTML widget. –>
<section id=”panorra-route-optimizer”>
<style>
#panorra-route-optimizer{font:16px/1.45 system-ui,-apple-system,”Segoe UI”,sans-serif;color:#17212b;max-width:960px;margin:auto}#panorra-route-optimizer *{box-sizing:border-box}#panorra-route-optimizer h2{margin:0 0 .35rem}#panorra-route-optimizer h3{margin:1.35rem 0 .25rem}#panorra-route-optimizer .hint{margin:.25rem 0 .8rem;color:#52606d}#panorra-route-optimizer input,#panorra-route-optimizer textarea,#panorra-route-optimizer select{width:100%;border:1px solid #a8b3bf;border-radius:6px;padding:.7rem;font:14px ui-monospace,SFMono-Regular,Consolas,monospace}#panorra-route-optimizer textarea{min-height:220px;resize:vertical}#panorra-route-optimizer label{display:block;font-weight:650;margin:.8rem 0 .3rem}#panorra-route-optimizer button{border:0;border-radius:6px;padding:.8rem 1.05rem;background:#1266d6;color:#fff;font-weight:700;cursor:pointer;margin-top:1rem}#panorra-route-optimizer button:disabled{background:#8491a0;cursor:not-allowed}#panorra-route-optimizer .status,#panorra-route-optimizer .output{margin-top:1rem;padding:.8rem;border-radius:6px;background:#f2f5f7;white-space:pre-line}#panorra-route-optimizer .error{background:#fff0f0;color:#9b1c1c}#panorra-route-optimizer .success{background:#e9f9ee;color:#176b32}@media(max-width:600px){#panorra-route-optimizer{font-size:15px}#panorra-route-optimizer button{width:100%}}
</style>
<h2>Matrix Route Optimizer</h2>
<p class=”hint”>This page uses the uploaded read-only OSRM matrix only. It never requests OSRM during optimization.</p>
<label for=”pro-matrix”>Upload Uint16 Matrix</label><input id=”pro-matrix” type=”file” accept=”.uint16,application/octet-stream”>
<label for=”pro-mapping”>Upload GPS Mapping</label><input id=”pro-mapping” type=”file” accept=”.txt,text/plain”>
<label for=”pro-metadata”>Upload Matrix Metadata</label><input id=”pro-metadata” type=”file” accept=”.txt,text/plain”>
<h3>Route endpoints</h3>
<label for=”pro-start”>Start Point</label><input id=”pro-start” value=”33.5871849,73.1193211″ spellcheck=”false”>
<label for=”pro-end-preset”>End Point preset</label>
<select id=”pro-end-preset”><option value=”33.5871419,73.1192698″>Shop — 33.5871419,73.1192698</option><option value=”33.6245854,73.1555715″>Bilal Khokar — 33.6245854,73.1555715</option><option value=”33.6076387,73.0093968″>Faiz Ur Rahman — 33.6076387,73.0093968</option><option value=”33.6538281,73.0556860″>Home — 33.6538281,73.0556860</option></select>
<label for=”pro-end”>End Point</label><input id=”pro-end” value=”33.5871419,73.1192698″ spellcheck=”false”>
<label for=”pro-deliveries”>Delivery locations (one GPS coordinate per line)</label><textarea id=”pro-deliveries” spellcheck=”false” placeholder=”33.5932081,73.0998070 33.5851700,73.1079971″></textarea>
<label for=”pro-copy-threshold”>Non-shared worker matrix threshold (MB)</label><input id=”pro-copy-threshold” type=”number” min=”1″ step=”1″ value=”32″ aria-label=”Non-shared worker matrix threshold in megabytes”>
<p class=”hint”>When shared memory is unavailable, matrices at or below this size may use ordinary workers. Larger matrices use the memory-safe single-thread mode.</p>
<button id=”pro-optimize” type=”button”>Optimize Route</button>
<div id=”pro-status” class=”status” role=”status”>Status: Upload the three matrix files, then enter the route locations.</div>
<textarea id=”pro-output” class=”output” readonly aria-label=”Optimized GPS route” placeholder=”The optimized route will appear here and be copied to the clipboard.”></textarea>
<script>
(function () {
‘use strict’;
var $ = function (id) { return document.getElementById(id); };
var matrixFile = $(‘pro-matrix’), mappingFile = $(‘pro-mapping’), metadataFile = $(‘pro-metadata’), startInput = $(‘pro-start’), endInput = $(‘pro-end’), preset = $(‘pro-end-preset’), deliveriesInput = $(‘pro-deliveries’), copyThresholdInput = $(‘pro-copy-threshold’), optimize = $(‘pro-optimize’), status = $(‘pro-status’), output = $(‘pro-output’);
function setStatus(text, kind) { status.textContent = text; status.className = ‘status’ + (kind ? ‘ ‘ + kind : ”); }
function number(value) { return value.toLocaleString(); }
function parseCoordinate(raw, label) { var match = raw.trim().match(/^([+-]?(?:\d+(?:\.\d*)?|\.\d+))\s*,\s*([+-]?(?:\d+(?:\.\d*)?|\.\d+))$/); if (!match) throw new Error(label + ‘ is not a valid latitude,longitude coordinate.’); var lat = Number(match[1]), lon = Number(match[2]); if (!Number.isFinite(lat) || !Number.isFinite(lon) || lat < -90 || lat > 90 || lon < -180 || lon > 180) throw new Error(label + ‘ is outside valid GPS bounds.’); return { raw: raw, key: lat + ‘,’ + lon }; }
function parseMetadata(text) { var fields = {}; text.split(/\r?\n/).forEach(function (line) { var at = line.indexOf(‘=’); if (at > 0) fields[line.slice(0, at).trim()] = line.slice(at + 1).trim(); }); var required = { FORMAT: ‘PANORRA_UINT16_MATRIX’, VERSION: ‘1’, UNIT: ‘METERS’, TYPE: ‘DIRECTED’, ARRAY: ‘Uint16Array’ }; Object.keys(required).forEach(function (key) { if (fields[key] !== required[key]) throw new Error(‘Metadata validation failed: ‘ + key + ‘ must be ‘ + required[key] + ‘.’); }); [‘LAYOUT’,’BYTE_ORDER’,’BYTES_PER_VALUE’].forEach(function (key) { var expected = { LAYOUT:’ROW_MAJOR’, BYTE_ORDER:’LITTLE_ENDIAN’, BYTES_PER_VALUE:’2′ }[key]; if (fields[key] && fields[key] !== expected) throw new Error(‘Metadata validation failed: ‘ + key + ‘ must be ‘ + expected + ‘.’); }); var count = Number(fields.COUNT); if (!Number.isSafeInteger(count) || count < 1) throw new Error(‘Metadata validation failed: COUNT must be a positive integer.’); return count; }
function parseMapping(text, count) { var byKey = new Map(), rows = text.split(/\r?\n/).filter(function (line) { return line !== ”; }); if (rows.length !== count) throw new Error(‘GPS mapping count does not match metadata COUNT.’); rows.forEach(function (line, index) { var at = line.indexOf(‘|’), id = at > 0 ? line.slice(0, at) : ”; if (id !== ‘G’ + (index + 1)) throw new Error(‘GPS mapping must be ordered sequentially from G1.’); var point = parseCoordinate(line.slice(at + 1), ‘Mapping row G’ + (index + 1)); if (byKey.has(point.key)) throw new Error(‘GPS mapping contains duplicate coordinate ‘ + point.raw + ‘.’); byKey.set(point.key, index); }); return byKey; }
async function loadFiles() { var selected = [matrixFile.files[0], mappingFile.files[0], metadataFile.files[0]]; if (selected.filter(Boolean).length !== 3) throw new Error(‘Upload the Uint16 matrix, GPS mapping, and matrix metadata files.’); var loaded = await Promise.all([selected[0].arrayBuffer(), selected[1].text(), selected[2].text()]); var count = parseMetadata(loaded[2]), expected = count * count * 2; if (!Number.isSafeInteger(expected) || loaded[0].byteLength !== expected) throw new Error(‘Matrix file size does not match COUNT × COUNT × 2 bytes.’); return { matrix: new Uint16Array(loaded[0]), count: count, mapping: parseMapping(loaded[1], count) }; }
function parseDeliveries() { var result = []; deliveriesInput.value.split(/\r?\n/).forEach(function (raw, i) { if (raw.trim() !== ”) result.push(parseCoordinate(raw, ‘Delivery line ‘ + (i + 1))); }); if (!result.length) throw new Error(‘Paste at least one delivery location.’); return result; }
function indexFor(point, mapping, label) { var index = mapping.get(point.key); if (index === undefined) throw new Error(label + ‘ is not present in the uploaded GPS mapping.’); return index; }
function nearestNeighbor(start, end, deliveries, startRaw, endRaw, matrix, n) { var remaining = deliveries.slice(), route = [start], labels = [startRaw], current = start, distance = 0; while (remaining.length) { var bestAt = 0, bestDistance = Infinity; for (var i = 0; i < remaining.length; i++) { var candidate = matrix[current * n + remaining[i].index]; if (candidate < bestDistance) { bestDistance = candidate; bestAt = i; } } var next = remaining.splice(bestAt, 1)[0]; current = next.index; route.push(current); labels.push(next.raw); distance += bestDistance; } route.push(end); labels.push(endRaw); return { route: route, labels: labels, distance: distance + matrix[current * n + end] }; }
var workerCode = “let matrix,route,n,len,prefix;self.onmessage=e=>{let d=e.data;if(d.type===’init’){matrix=new Uint16Array(d.matrix);route=new Int32Array(d.route);prefix=new Float64Array(d.prefix);n=d.n;len=d.length;postMessage({type:’ready’});return}if(d.type===’scan’){if(d.reverse)for(let a=d.reverse.i,b=d.reverse.k;a<b;a++,b–){let v=route[a];route[a]=route[b];route[b]=v}if(d.prefix)prefix.set(d.prefix);let best=0,bi=-1,bk=-1;for(let i=d.start;i<=d.stop;i++)for(let k=i+1;k<=len-2;k++){let delta=matrix[route[i-1]*n+route[k]]+matrix[route[i]*n+route[k+1]]-matrix[route[i-1]*n+route[i]]-matrix[route[k]*n+route[k+1]]+prefix[k]-prefix[i];if(delta<best){best=delta;bi=i;bk=k}}postMessage({type:’result’,id:d.id,delta:best,i:bi,k:bk})}};”;
function makeSharedMatrix(matrix) { if (!window.crossOriginIsolated || typeof SharedArrayBuffer === ‘undefined’) return null; var buffer = new SharedArrayBuffer(matrix.byteLength); new Uint8Array(buffer).set(new Uint8Array(matrix.buffer)); return new Uint16Array(buffer); }
async function createPool(matrix, route, prefix, n, workerCount, shared) { var url = URL.createObjectURL(new Blob([workerCode], { type: ‘text/javascript’ })), workers = []; try { for (var i = 0; i < workerCount; i++) workers.push(new Worker(url)); await Promise.all(workers.map(function (worker) { return new Promise(function (resolve, reject) { worker.onmessage = function (event) { if (event.data.type === ‘ready’) resolve(); }; worker.onerror = function () { reject(new Error(‘A route worker could not start.’)); }; worker.postMessage({ type: ‘init’, matrix: matrix.buffer, route: route.buffer, prefix: prefix.buffer, n: n, length: route.length }); }); })); return { workers: workers, url: url, routeLength: route.length, shared: shared }; } catch (error) { workers.forEach(function (worker) { worker.terminate(); }); URL.revokeObjectURL(url); throw error; } }
function fillReversePrefix(route, matrix, n, prefix) { prefix[0] = 0; for (var i = 0; i < route.length – 1; i++) prefix[i + 1] = prefix[i] + matrix[route[i + 1] * n + route[i]] – matrix[route[i] * n + route[i + 1]]; }
function findBestMoveLocal(route, matrix, n, prefix) { var best = { delta: 0, i: -1, k: -1 }; for (var i = 1; i <= route.length – 3; i++) for (var k = i + 1; k <= route.length – 2; k++) { var delta = matrix[route[i – 1] * n + route[k]] + matrix[route[i] * n + route[k + 1]] – matrix[route[i – 1] * n + route[i]] – matrix[route[k] * n + route[k + 1]] + prefix[k] – prefix[i]; if (delta < best.delta) best = { delta: delta, i: i, k: k }; } return best; }
function findBestMove(pool, id, reverse, prefix) { var eligible = pool.routeLength – 3, workers = pool.workers, jobs = [], start = 1; for (var w = 0; w < workers.length && start <= eligible; w++) { var size = Math.ceil((eligible – start + 1) / (workers.length – w)), stop = start + size – 1, worker = workers[w]; jobs.push(new Promise(function (resolve, reject) { worker.onmessage = function (event) { if (event.data.type === ‘result’ && event.data.id === id) resolve(event.data); }; worker.onerror = function () { reject(new Error(‘A route worker failed during 2-opt scanning.’)); }; worker.postMessage({ type: ‘scan’, id: id, start: start, stop: stop, reverse: pool.shared ? null : reverse, prefix: pool.shared ? null : prefix }); })); start = stop + 1; } return Promise.all(jobs).then(function (results) { return results.reduce(function (best, move) { return move.delta < best.delta ? move : best; }, { delta: 0, i: -1, k: -1 }); }); }
function reverseRange(values, first, last) { while (first < last) { var value = values[first]; values[first++] = values[last]; values[last–] = value; } }
function copyToClipboard(text) { if (navigator.clipboard && window.isSecureContext) return navigator.clipboard.writeText(text); output.focus(); output.select(); return Promise.resolve(document.execCommand(‘copy’)); }
preset.addEventListener(‘change’, function () { endInput.value = preset.value; });
optimize.addEventListener(‘click’, async function () { var pool; try { optimize.disabled = true; output.value = ”; setStatus(‘Status: Validating uploaded files…’); var data = await loadFiles(), startPoint = parseCoordinate(startInput.value, ‘Start Point’), endPoint = parseCoordinate(endInput.value, ‘End Point’), deliveryPoints = parseDeliveries(), thresholdMB = Number(copyThresholdInput.value), thresholdBytes = thresholdMB * 1024 * 1024; if (!Number.isFinite(thresholdMB) || thresholdMB <= 0 || !Number.isSafeInteger(thresholdBytes)) throw new Error(‘Enter a positive non-shared worker matrix threshold in MB.’); var start = indexFor(startPoint, data.mapping, ‘Start Point’), end = indexFor(endPoint, data.mapping, ‘End Point’), seenDeliveries = new Set(), removedDuplicates = 0, deliveries = deliveryPoints.map(function (point, i) { return { index: indexFor(point, data.mapping, ‘Delivery line ‘ + (i + 1)), raw: point.raw }; }).filter(function (delivery) { if (seenDeliveries.has(delivery.index)) { removedDuplicates++; return false; } seenDeliveries.add(delivery.index); return true; }), deliveryIndexes = deliveries.map(function (delivery) { return delivery.index; }); if (start === end || deliveryIndexes.indexOf(start) !== -1 || deliveryIndexes.indexOf(end) !== -1) throw new Error(‘Start Point and End Point must be different from all delivery locations.’); var started = performance.now(), initial = nearestNeighbor(start, end, deliveries, startPoint.raw, endPoint.raw, data.matrix, data.count), sharedMatrix = makeSharedMatrix(data.matrix), routeLabels = initial.labels, route, currentDistance = initial.distance, workerCount = Math.max(1, navigator.hardwareConcurrency || 1), prefix, sharedAvailable = !!sharedMatrix, ordinaryWorkersAllowed = !sharedAvailable && data.matrix.byteLength <= thresholdBytes, pendingReverse = null; if (sharedAvailable) { data.matrix = sharedMatrix; route = new Int32Array(new SharedArrayBuffer(initial.route.length * Int32Array.BYTES_PER_ELEMENT)); route.set(initial.route); prefix = new Float64Array(new SharedArrayBuffer(route.length * Float64Array.BYTES_PER_ELEMENT)); pool = await createPool(data.matrix, route, prefix, data.count, workerCount, true); setStatus(‘SharedArrayBuffer: available\nWorkers in use: ‘ + number(pool.workers.length) + ‘ / ‘ + number(workerCount) + ‘\nStatus: Exhaustively checking 2-opt moves with one shared matrix and prefix…’); } else { route = new Int32Array(initial.route); prefix = new Float64Array(route.length); if (ordinaryWorkersAllowed) { pool = await createPool(data.matrix, route, prefix, data.count, workerCount, false); setStatus(‘SharedArrayBuffer: unavailable\nMulti-worker shared-memory optimization: unavailable on this page\nWorkers in use: ‘ + number(pool.workers.length) + ‘ ordinary workers\nStatus: Matrix is within the ‘ + number(thresholdMB) + ‘ MB copy threshold.’); } else setStatus(‘SharedArrayBuffer: unavailable\nMulti-worker shared-memory optimization: unavailable on this page\nWorkers in use: 0\nStatus: Matrix exceeds the ‘ + number(thresholdMB) + ‘ MB copy threshold; using memory-safe single-thread exhaustive 2-opt.’); } var iteration = 0; while (true) { fillReversePrefix(route, data.matrix, data.count, prefix); var move = pool ? await findBestMove(pool, iteration, pendingReverse, prefix) : findBestMoveLocal(route, data.matrix, data.count, prefix); iteration++; if (move.delta >= 0) break; pendingReverse = { i: move.i, k: move.k }; reverseRange(route, move.i, move.k); reverseRange(routeLabels, move.i, move.k); currentDistance += move.delta; setStatus(‘SharedArrayBuffer: ‘ + (sharedAvailable ? ‘available’ : ‘unavailable’) + ‘\nWorkers in use: ‘ + number(pool ? pool.workers.length : 0) + (pool && !pool.shared ? ‘ ordinary workers’ : ”) + ‘\nDelivery Locations: ‘ + number(deliveries.length) + (removedDuplicates ? ‘\nDuplicate GPS automatically removed: ‘ + number(removedDuplicates) : ”) + ‘\n2-opt iterations completed: ‘ + number(iteration) + ‘\nStatus: Exhaustively checking all remaining valid 2-opt moves…’); } var routeText = routeLabels.join(‘\n’); output.value = routeText; try { await copyToClipboard(routeText); } catch (ignore) {} var elapsed = ((performance.now() – started) / 1000).toFixed(2), improvement = initial.distance – currentDistance; setStatus(‘Optimization complete — route copied to clipboard.\n\nSharedArrayBuffer: ‘ + (sharedAvailable ? ‘available’ : ‘unavailable’) + ‘\nWorkers used: ‘ + number(pool ? pool.workers.length : 0) + (pool && !pool.shared ? ‘ ordinary workers’ : pool ? ” : ‘ (memory-safe fallback)’) + ‘\nDelivery Locations: ‘ + number(deliveries.length) + (removedDuplicates ? ‘\nDuplicate GPS automatically removed: ‘ + number(removedDuplicates) : ”) + ‘\nInitial Nearest Neighbor Distance: ‘ + number(initial.distance) + ‘ m\nFinal 2-opt Distance: ‘ + number(currentDistance) + ‘ m\nImprovement: ‘ + number(improvement) + ‘ m\nOptimization Time: ‘ + elapsed + ‘ seconds’, ‘success’); } catch (error) { setStatus(‘Status: ‘ + error.message, ‘error’); } finally { if (pool) { pool.workers.forEach(function (worker) { worker.terminate(); }); URL.revokeObjectURL(pool.url); } optimize.disabled = false; } });
}());
</script>
</section>