264 lines
10 KiB
JavaScript
264 lines
10 KiB
JavaScript
document.addEventListener('DOMContentLoaded', function() {
|
|
var avoidHighwaysCheckbox = document.getElementById('avoidHighways');
|
|
var useShortestCheckbox = document.getElementById('useShortest');
|
|
var avoidTollRoadsCheckbox = document.getElementById('avoidTollRoads');
|
|
var avoidFerriesCheckbox = document.getElementById('avoidFerries');
|
|
var avoidUnpavedCheckbox = document.getElementById('avoidUnpaved');
|
|
var points = [];
|
|
var markers = [];
|
|
var routePolyline = null;
|
|
|
|
function calculateRoute() {
|
|
if (points.length === 2) {
|
|
var moto = {};
|
|
// Avoid highways -> lower highway usage weight
|
|
if (avoidHighwaysCheckbox && avoidHighwaysCheckbox.checked) {
|
|
moto.use_highways = 0.0; // 0..1 (0 avoids, 1 prefers)
|
|
}
|
|
// Avoid ferries -> lower ferry usage weight
|
|
if (avoidFerriesCheckbox && avoidFerriesCheckbox.checked) {
|
|
moto.use_ferry = 0.0; // 0..1
|
|
}
|
|
// Avoid unpaved -> exclude unpaved roads entirely
|
|
if (avoidUnpavedCheckbox && avoidUnpavedCheckbox.checked) {
|
|
moto.exclude_unpaved = true;
|
|
}
|
|
// Avoid tolls -> exclude tolls
|
|
if (avoidTollRoadsCheckbox && avoidTollRoadsCheckbox.checked) {
|
|
moto.exclude_tolls = true;
|
|
}
|
|
|
|
var costing_options = { motorcycle: moto };
|
|
|
|
var requestBody = {
|
|
locations: [
|
|
{ lat: points[0].lat, lon: points[0].lng },
|
|
{ lat: points[1].lat, lon: points[1].lng }
|
|
],
|
|
costing: "motorcycle",
|
|
costing_options: costing_options,
|
|
units: "kilometers"
|
|
};
|
|
if (useShortestCheckbox && useShortestCheckbox.checked) {
|
|
requestBody.shortest = true; // top-level shortest flag
|
|
}
|
|
|
|
fetch('/route', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify(requestBody)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
var leg = data.trip && data.trip.legs && data.trip.legs[0];
|
|
var infoCard = document.getElementById('routeInfoCard');
|
|
if (leg && leg.summary && typeof leg.summary.length === 'number' && typeof leg.summary.time === 'number') {
|
|
var distanceKm = (leg.summary.length).toFixed(1); // already in km
|
|
var durationMin = Math.round(leg.summary.time / 60); // seconds to minutes
|
|
var info = `<strong>Distance:</strong> ${distanceKm} km<br>
|
|
<strong>Estimated Time:</strong> ${durationMin} min<br>
|
|
<strong>Motorcycle Profile</strong>`;
|
|
infoCard.innerHTML = info;
|
|
infoCard.classList.remove('d-none');
|
|
} else {
|
|
infoCard.innerHTML = `<strong>Route info unavailable.</strong>`;
|
|
infoCard.classList.remove('d-none');
|
|
console.log('Valhalla response:', data);
|
|
}
|
|
var latlngs = decodePolyline6(leg.shape);
|
|
if (routePolyline) {
|
|
map.removeLayer(routePolyline);
|
|
}
|
|
routePolyline = L.polyline(latlngs, { color: 'red', weight: 5}).addTo(map);
|
|
map.fitBounds(routePolyline.getBounds());
|
|
});
|
|
}
|
|
}
|
|
|
|
map.on('click', function(e) {
|
|
if (points.length < 2) {
|
|
var marker = L.marker(e.latlng).addTo(map);
|
|
markers.push(marker);
|
|
points.push(e.latlng);
|
|
marker.bindPopup(points.length === 1 ? "Start" : "End").openPopup();
|
|
|
|
// Reverse geocode and fill address field
|
|
if (points.length === 1) {
|
|
reverseGeocode(e.latlng.lat, e.latlng.lng, 'sourceInput');
|
|
} else if (points.length === 2) {
|
|
reverseGeocode(e.latlng.lat, e.latlng.lng, 'destInput');
|
|
}
|
|
}
|
|
calculateRoute();
|
|
});
|
|
|
|
// Listen for changes on all checkboxes
|
|
[
|
|
avoidHighwaysCheckbox,
|
|
useShortestCheckbox,
|
|
avoidTollRoadsCheckbox,
|
|
avoidFerriesCheckbox,
|
|
avoidUnpavedCheckbox
|
|
].forEach(function(checkbox) {
|
|
if (checkbox) {
|
|
checkbox.addEventListener('change', calculateRoute);
|
|
}
|
|
});
|
|
|
|
// Disable other checkboxes when "Use shortest route" is checked
|
|
useShortestCheckbox.addEventListener('change', function() {
|
|
var disable = useShortestCheckbox.checked;
|
|
[
|
|
avoidHighwaysCheckbox,
|
|
avoidTollRoadsCheckbox,
|
|
avoidFerriesCheckbox,
|
|
avoidUnpavedCheckbox
|
|
].forEach(function(cb) {
|
|
cb.disabled = disable;
|
|
});
|
|
});
|
|
|
|
// Adapted from Valhalla docs — polyline6 decoder for JS
|
|
function decodePolyline6(encoded) {
|
|
var coords = [];
|
|
var index = 0, lat = 0, lng = 0;
|
|
var shift = 0, result = 0, byte = null, latitude_change, longitude_change;
|
|
var factor = Math.pow(10, 6);
|
|
while (index < encoded.length) {
|
|
byte = shift = result = 0;
|
|
do {
|
|
byte = encoded.charCodeAt(index++) - 63;
|
|
result |= (byte & 0x1f) << shift;
|
|
shift += 5;
|
|
} while (byte >= 0x20);
|
|
latitude_change = (result & 1) ? ~(result >> 1) : (result >> 1);
|
|
|
|
shift = result = 0;
|
|
do {
|
|
byte = encoded.charCodeAt(index++) - 63;
|
|
result |= (byte & 0x1f) << shift;
|
|
shift += 5;
|
|
} while (byte >= 0x20);
|
|
longitude_change = (result & 1) ? ~(result >> 1) : (result >> 1);
|
|
|
|
lat += latitude_change;
|
|
lng += longitude_change;
|
|
|
|
coords.push([lat / factor, lng / factor]);
|
|
}
|
|
return coords;
|
|
}
|
|
|
|
// Remove Markers
|
|
function resetMarkers() {
|
|
markers.forEach(function(marker) {
|
|
map.removeLayer(marker);
|
|
});
|
|
markers = [];
|
|
points = [];
|
|
if (routePolyline) {
|
|
map.removeLayer(routePolyline);
|
|
routePolyline = null;
|
|
}
|
|
// Clear address fields
|
|
var sourceInput = document.getElementById('sourceInput');
|
|
var destInput = document.getElementById('destInput');
|
|
sourceInput.value = '';
|
|
destInput.value = '';
|
|
delete sourceInput.dataset.lat;
|
|
delete sourceInput.dataset.lon;
|
|
delete destInput.dataset.lat;
|
|
delete destInput.dataset.lon;
|
|
}
|
|
|
|
// Make resetMarkers available globally
|
|
window.resetMarkers = resetMarkers;
|
|
|
|
// Plot Route button logic
|
|
document.getElementById('plotRouteBtn').addEventListener('click', function() {
|
|
var sourceInput = document.getElementById('sourceInput');
|
|
var destInput = document.getElementById('destInput');
|
|
var sourceLat = parseFloat(sourceInput.dataset.lat);
|
|
var sourceLon = parseFloat(sourceInput.dataset.lon);
|
|
var destLat = parseFloat(destInput.dataset.lat);
|
|
var destLon = parseFloat(destInput.dataset.lon);
|
|
|
|
if (!isNaN(sourceLat) && !isNaN(sourceLon) && !isNaN(destLat) && !isNaN(destLon)) {
|
|
// Remove old markers
|
|
markers.forEach(function(marker) {
|
|
map.removeLayer(marker);
|
|
});
|
|
markers = [];
|
|
points = [];
|
|
|
|
// Add new markers
|
|
var startMarker = L.marker([sourceLat, sourceLon]).addTo(map).bindPopup("Start").openPopup();
|
|
var endMarker = L.marker([destLat, destLon]).addTo(map).bindPopup("End").openPopup();
|
|
markers.push(startMarker, endMarker);
|
|
points.push({lat: sourceLat, lng: sourceLon}, {lat: destLat, lng: destLon});
|
|
|
|
calculateRoute();
|
|
} else {
|
|
alert("Please enter valid addresses for both start and destination.");
|
|
}
|
|
});
|
|
|
|
function reverseGeocode(lat, lon, inputId) {
|
|
fetch(`/reverse?format=json&lat=${lat}&lon=${lon}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
var input = document.getElementById(inputId);
|
|
if (data && data.address) {
|
|
// Use the same format as your autocomplete
|
|
input.value = formatAddress(data);
|
|
} else {
|
|
input.value = `${lat}, ${lon}`;
|
|
}
|
|
input.dataset.lat = lat;
|
|
input.dataset.lon = lon;
|
|
});
|
|
}
|
|
|
|
function exportRouteAsGPX(latlngs) {
|
|
if (!latlngs || latlngs.length === 0) {
|
|
alert("No route to export.");
|
|
return;
|
|
}
|
|
let gpx =
|
|
`<?xml version="1.0" encoding="UTF-8"?>
|
|
<gpx version="1.1" creator="FreeMoto" xmlns="http://www.topografix.com/GPX/1/1">
|
|
<trk>
|
|
<name>FreeMoto Route</name>
|
|
<trkseg>
|
|
${latlngs.map(pt => ` <trkpt lat="${pt[0]}" lon="${pt[1]}"></trkpt>`).join('\n')}
|
|
</trkseg>
|
|
</trk>
|
|
</gpx>`;
|
|
|
|
let blob = new Blob([gpx], {type: "application/gpx+xml"});
|
|
let url = URL.createObjectURL(blob);
|
|
|
|
// iOS workaround: open in new tab instead of triggering download
|
|
if (navigator.userAgent.match(/(iPad|iPhone|iPod)/i)) {
|
|
window.open(url, '_blank');
|
|
} else {
|
|
let a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = "freemoto-route.gpx";
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
}
|
|
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
}
|
|
|
|
document.getElementById('exportGpxBtn').addEventListener('click', function() {
|
|
if (routePolyline) {
|
|
// routePolyline.getLatLngs() returns array of LatLng objects
|
|
let latlngs = routePolyline.getLatLngs().map(ll => [ll.lat, ll.lng]);
|
|
exportRouteAsGPX(latlngs);
|
|
} else {
|
|
alert("No route to export.");
|
|
}
|
|
});
|
|
}); |