Remember how much of a game-changer Nextbase AutoRoute was, back in the day?
In 1989, it was the absolute gold standard for planning a trip, all without needing a crumb of internet data. It felt like you were wielding some serious power, mapping out your own routes on your own terms.
That old-school, hands-on control is exactly what we are reviving here, just with a much sleeker, modern twist.
This route planner widget basically takes that same spirit and drops it right into your website, but turns the utility up to eleven. You aren't just limited to staring at a screen anymore.
Now, it generates a clean, downloadable PDF of your route itinerary, which is a massive upgrade.
Whether you need a crisp printout for the dashboard or just want the file saved on your phone for easy access while you are out walking or driving, the information follows you.
It is the perfect blend of that classic, tactical feel with the convenience we actually expect from tech today.
What's more, there are absolutely zero API keys to insert or complicated setups to wrestle with. You just drop the code in, and it works straight out of the box to find the shortest route.
Understanding Routing Terminology
Ever wonder what the routing engine is actually "thinking" when it plots your course? Navigation isn't just about drawing a line from A to B - it’s a complex interpretation of road geometry and traffic flow. When you use a routing machine, it breaks your journey down into a series of manoeuvres based on standardised geographic data. Understanding these terms helps you better visualise your trip before you even leave.
Road Circles vs. Roundabouts: While often used interchangeably, there is a distinction in how routing engines handle them. A roundabout is typically designed with specific deflection (curved entry) to force lower speeds, and the routing machine will explicitly label it as a "roundabout" maneuver. A road circle (or traffic circle) is often larger and can be found in older urban planning; these may not always have the same entry-speed constraints as modern roundabouts.
Merge vs. Junction: While both involve connecting roads, a merge implies a seamless entry onto a larger, often high-speed highway, whereas a junction is the specific node where different road segments meet or cross. Junctions typically require a change in priority, such as a stop sign or traffic light, which the routing engine logs as a decision point.
Slip Roads: Commonly seen on UK motorways, these are the dedicated lanes used to transition on or off high-speed roads. The routing engine treats these as specific "link" segments that bridge the gap between local streets and major highways.
Vector Manoeuvres: This is how software translates real-world physics into visual instructions. Whether it is a slight-right or a U-turn, the engine categorises the angle of the turn to provide you with the most accurate directional guidance. By mapping these technical terms to real-world visual icons, you can transform complex map data into a clear, easy-to-read itinerary. Happy navigating!
Route Planner
Buy Me A Coffee
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet-routing-machine@latest/dist/leaflet-routing-machine.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-routing-machine@latest/dist/leaflet-routing-machine.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.31/jspdf.plugin.autotable.min.js"></script>
<style>
.ms-autoroute-widget {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: #ffffff;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
padding: 20px;
max-width: 100%;
box-sizing: border-box;
border: 1px solid #e5e7eb;
}
.route-header {
margin: 0 0 15px 0;
font-size: 18px;
font-weight: 600;
color: #111827;
display: flex;
align-items: center;
gap: 8px;
}
.route-icon {
color: #2563eb;
flex-shrink: 0;
}
.route-inputs {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 15px;
}
.route-inputs input {
padding: 10px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 14px;
outline: none;
transition: border-color 0.2s;
}
.route-inputs input:focus {
border-color: #2563eb;
}
.route-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.route-btn {
background: #2563eb;
color: white;
border: none;
padding: 10px;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
min-width: 140px;
}
.route-btn:hover { background: #1d4ed8; }
.reset-btn {
background: #ef4444;
color: white;
border: none;
padding: 10px;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
min-width: 100px;
}
.reset-btn:hover { background: #dc2826; }
.pdf-btn {
background: #10b981;
color: white;
border: none;
padding: 10px;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
flex: 1;
display: none;
align-items: center;
justify-content: center;
gap: 6px;
min-width: 140px;
}
.pdf-btn:hover { background: #059669; }
#autoroute-map {
height: 400px;
width: 100%;
border-radius: 8px;
z-index: 1;
}
.custom-route-pin {
background-color: #2563eb;
color: white;
width: 30px;
height: 30px;
border-radius: 50% 50% 50% 0;
transform: rotate(-45deg);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 2px 2px 5px rgba(0,0,0,0.3);
}
.custom-route-pin span {
transform: rotate(45deg);
font-weight: bold;
font-size: 14px;
}
.leaflet-routing-container {
background-color: white !important;
border-radius: 8px !important;
box-shadow: 0 2px 15px rgba(0,0,0,0.15) !important;
color: #333 !important;
margin-right: 10px !important;
margin-top: 10px !important;
padding-bottom: 10px !important;
}
.leaflet-routing-alt h2:first-of-type::before {
content: "ROUTE SUMMARY:";
display: block;
font-size: 11px !important;
text-transform: uppercase !important;
letter-spacing: 0.5px;
color: #6b7280 !important;
margin-bottom: 4px;
font-weight: 700;
}
.leaflet-routing-alt h2 {
padding: 15px 15px 5px 15px !important;
margin: 0 !important;
font-size: 15px !important;
font-weight: 600 !important;
color: #111827 !important;
line-height: 1.2 !important;
}
.leaflet-routing-alt h3 {
padding: 0 15px 15px 15px !important;
margin: 0 !important;
font-size: 14px !important;
font-weight: 500 !important;
color: #4b5563 !important;
}
.leaflet-routing-alt + .leaflet-routing-alt {
display: none !important;
}
.leaflet-routing-alt table tbody tr td:last-child {
white-space: nowrap !important;
min-width: 60px !important;
text-align: right;
}
</style>
<div class="ms-autoroute-widget" id="ms-autoroute-wrapper">
<h3 class="route-header">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="route-icon">
<polygon points="3 6 9 3 15 6 21 3 21 18 15 21 9 18 3 21"></polygon>
<line x1="9" y1="3" x2="9" y2="18"></line>
<line x1="15" y1="6" x2="15" y2="21"></line>
</svg>
Route Planner
</h3>
<div class="route-inputs">
<input type="text" id="ar-start" placeholder="Start (e.g., Carlisle)">
<input type="text" id="ar-end" placeholder="Destination (e.g., Edinburgh)">
<div class="route-actions">
<button id="ar-calc-btn" class="route-btn">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon>
</svg>
Calculate
</button>
<button id="ar-reset-btn" class="reset-btn">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path>
<path d="M3 3v5h5"></path>
</svg>
Reset
</button>
<button id="ar-pdf-btn" class="pdf-btn">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
Download PDF
</button>
</div>
</div>
<div id="autoroute-map"></div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
const map = L.map('autoroute-map', { attributionControl: false }).setView([54.51, -3.53], 6);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: ''
}).addTo(map);
let routingControl = null;
const calcBtn = document.getElementById('ar-calc-btn');
const resetBtn = document.getElementById('ar-reset-btn');
const pdfBtn = document.getElementById('ar-pdf-btn');
const calcBtnOriginalHTML = calcBtn.innerHTML;
async function geocode(locationText) {
const response = await fetch('https://nominatim.openstreetmap.org/search?format=json&q=' + encodeURIComponent(locationText));
const data = await response.json();
if (data && data.length > 0) {
return L.latLng(data[0].lat, data[0].lon);
}
throw new Error('Could not find location: ' + locationText);
}
// Mapping Leaflet turn classes to internal types for vector drawing
function getTurnType(htmlElement) {
const iconSpan = htmlElement.querySelector('.leaflet-routing-icon');
if (!iconSpan) return '';
const classes = iconSpan.className;
if (classes.includes('depart')) return 'start';
if (classes.includes('arrive')) return 'end';
if (classes.includes('turn-right')) return 'right';
if (classes.includes('turn-left')) return 'left';
if (classes.includes('slight-right')) return 'slight-right';
if (classes.includes('slight-left')) return 'slight-left';
if (classes.includes('sharp-right')) return 'sharp-right';
if (classes.includes('sharp-left')) return 'sharp-left';
if (classes.includes('u-turn')) return 'u-turn';
if (classes.includes('continue') || classes.includes('straight')) return 'straight';
return 'straight';
}
calcBtn.addEventListener('click', async () => {
const startText = document.getElementById('ar-start').value;
const endText = document.getElementById('ar-end').value;
if (!startText || !endText) {
alert("Please enter both a start and destination.");
return;
}
calcBtn.innerHTML = "Calculating...";
try {
const [startLatLng, endLatLng] = await Promise.all([
geocode(startText),
geocode(endText)
]);
if (routingControl) {
map.removeControl(routingControl);
}
routingControl = L.Routing.control({
waypoints: [startLatLng, endLatLng],
routeWhileDragging: true,
showAlternatives: false,
units: 'imperial',
lineOptions: {
styles: [{color: '#2563eb', opacity: 0.8, weight: 5}]
},
createMarker: function(i, wp, nWps) {
const label = i === 0 ? 'A' : (i === nWps - 1 ? 'B' : '');
if (!label) return null;
const icon = L.divIcon({
className: '',
html: '<div class="custom-route-pin"><span>' + label + '</span></div>',
iconSize: [30, 30],
iconAnchor: [15, 30]
});
return L.marker(wp.latLng, {icon: icon});
}
}).addTo(map);
pdfBtn.style.display = 'flex';
} catch (error) {
alert(error.message);
} finally {
calcBtn.innerHTML = calcBtnOriginalHTML;
}
});
resetBtn.addEventListener('click', () => {
document.getElementById('ar-start').value = '';
document.getElementById('ar-end').value = '';
if (routingControl) {
map.removeControl(routingControl);
routingControl = null;
}
map.setView([54.51, -3.53], 6);
pdfBtn.style.display = 'none';
});
pdfBtn.addEventListener('click', async () => {
if (!routingControl || !routingControl._routes || routingControl._routes.length === 0) {
alert("Please calculate a route first.");
return;
}
const pdfBtnOriginalHTML = pdfBtn.innerHTML;
pdfBtn.innerHTML = "Building PDF...";
try {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
const startStr = document.getElementById('ar-start').value || 'Start';
const endStr = document.getElementById('ar-end').value || 'Destination';
// --- NEW POLISHED HEADER ---
// Top colored banner
doc.setFillColor(31, 41, 55);
doc.rect(0, 0, 210, 35, 'F');
// Title
doc.setFont("helvetica", "bold");
doc.setFontSize(22);
doc.setTextColor(255, 255, 255);
doc.text("A to B: Route Planner", 14, 22);
// Subtext
doc.setFontSize(11);
doc.setFont("helvetica", "normal");
doc.setTextColor(156, 163, 175);
doc.text(`${startStr} to ${endStr}`, 14, 29);
// Summary details box
doc.setFillColor(243, 244, 246);
doc.roundedRect(14, 42, 182, 22, 3, 3, 'F');
doc.setFontSize(11);
doc.setFont("helvetica", "bold");
doc.setTextColor(17, 24, 39);
let summaryText = "";
let distanceText = "";
const summaryH2 = document.querySelector('.leaflet-routing-alt h2');
const summaryH3 = document.querySelector('.leaflet-routing-alt h3');
if (summaryH2) summaryText = summaryH2.innerText;
if (summaryH3) distanceText = summaryH3.innerText;
doc.text(summaryText, 20, 50);
doc.setFontSize(10);
doc.setFont("helvetica", "normal");
doc.setTextColor(75, 85, 99);
doc.text(`Total Route Distance & Time: ${distanceText}`, 20, 57);
// --- PROCESS TABLE DATA ---
const tableRows = [];
const trs = document.querySelectorAll('.leaflet-routing-alt tbody tr');
trs.forEach(function(tr) {
const tds = tr.querySelectorAll('td');
if (tds.length >= 3) {
const turnType = getTurnType(tds[0]);
const instruction = tds[1].innerText.trim();
tableRows.push([turnType, instruction, tds[2].innerText]);
} else if (tds.length === 2) {
tableRows.push(['', tds[0].innerText, tds[1].innerText]);
}
});
// --- RENDER AUTOTABLE WITH VECTOR SYMBOLS ---
doc.autoTable({
startY: 72,
head: [['', 'Directions', 'Distance']],
body: tableRows,
theme: 'grid',
headStyles: { fillColor: [37, 99, 235], textColor: 255, fontStyle: 'bold', halign: 'left' },
bodyStyles: { fontSize: 10, cellPadding: 6, textColor: [55, 65, 81] },
alternateRowStyles: { fillColor: [249, 250, 251] },
columnStyles: {
0: { cellWidth: 15, halign: 'center' }, // Symbol column
1: { cellWidth: 135 },
2: { cellWidth: 32, halign: 'right', fontStyle: 'bold', textColor: [37, 99, 235] }
},
didParseCell: function(data) {
// Hide the text string in the symbol column so we can draw the icon instead
if (data.column.index === 0 && data.section === 'body') {
data.cell.text = '';
}
},
didDrawCell: function(data) {
if (data.column.index === 0 && data.section === 'body') {
const turnType = tableRows[data.row.index][0];
if (!turnType) return;
const x = data.cell.x + data.cell.width / 2;
const y = data.cell.y + data.cell.height / 2;
doc.setDrawColor(75, 85, 99);
doc.setLineWidth(0.8);
// Vector drawing for each turn type
if (turnType === 'start') {
doc.setFillColor(34, 197, 94); // Green
doc.setDrawColor(34, 197, 94);
doc.circle(x, y, 2.5, 'FD');
} else if (turnType === 'end') {
doc.setFillColor(239, 68, 68); // Red
doc.setDrawColor(239, 68, 68);
doc.circle(x, y, 2.5, 'FD');
} else if (turnType === 'straight') {
doc.line(x, y + 2.5, x, y - 2.5); // vertical line
doc.line(x, y - 2.5, x - 1.5, y - 0.5); // left arrow head
doc.line(x, y - 2.5, x + 1.5, y - 0.5); // right arrow head
} else if (turnType === 'right' || turnType === 'sharp-right') {
doc.line(x - 2.5, y, x + 2.5, y); // horizontal
doc.line(x + 2.5, y, x + 0.5, y - 1.5);
doc.line(x + 2.5, y, x + 0.5, y + 1.5);
} else if (turnType === 'left' || turnType === 'sharp-left') {
doc.line(x + 2.5, y, x - 2.5, y);
doc.line(x - 2.5, y, x - 0.5, y - 1.5);
doc.line(x - 2.5, y, x - 0.5, y + 1.5);
} else if (turnType === 'slight-right') {
doc.line(x - 1.5, y + 1.5, x + 1.5, y - 1.5); // diagonal up-right
doc.line(x + 1.5, y - 1.5, x, y - 1.5);
doc.line(x + 1.5, y - 1.5, x + 1.5, y);
} else if (turnType === 'slight-left') {
doc.line(x + 1.5, y + 1.5, x - 1.5, y - 1.5); // diagonal up-left
doc.line(x - 1.5, y - 1.5, x, y - 1.5);
doc.line(x - 1.5, y - 1.5, x - 1.5, y);
} else if (turnType === 'u-turn') {
doc.line(x + 1.5, y - 1.5, x + 1.5, y + 0.5); // right drop
doc.line(x + 1.5, y + 0.5, x - 1.5, y + 0.5); // bottom
doc.line(x - 1.5, y + 0.5, x - 1.5, y - 1.5); // left rise
doc.line(x - 1.5, y - 1.5, x - 2.5, y - 0.5); // left arrow head 1
doc.line(x - 1.5, y - 1.5, x - 0.5, y - 0.5); // left arrow head 2
}
}
}
});
doc.save('Route_Directions.pdf');
} catch (err) {
console.error("PDF generation failed:", err);
alert("Failed to build PDF. Please check the console.");
} finally {
pdfBtn.innerHTML = pdfBtnOriginalHTML;
}
});
});
</script>
Notice:
Comments are moderated and may not appear immediately. Please keep your comments respectful, and relevant to the post. Spam will not be tolerated. My site. My rules.