|
1 | | -let cities = []; |
2 | | - |
3 | | -// Load CSV file dynamically |
4 | | -Papa.parse("uscities.csv", { |
5 | | - download: true, |
6 | | - header: true, |
7 | | - complete: (results) => { |
8 | | - cities = results.data; |
9 | | - console.log("Loaded cities:", cities.length); |
10 | | - } |
11 | | -}); |
12 | | - |
13 | | -// Pick a random city filtered by min population |
14 | | -function randomCity(minPop) { |
15 | | - const filtered = cities.filter(c => parseInt(c.population) > minPop); |
16 | | - return filtered[Math.floor(Math.random() * filtered.length)]; |
17 | | -} |
18 | | - |
19 | | -// Haversine distance calculator (miles) |
20 | | -function haversine(lat1, lon1, lat2, lon2) { |
21 | | - const R = 3956; |
22 | | - const toRad = x => (x * Math.PI) / 180; |
23 | | - |
24 | | - const dLat = toRad(lat2 - lat1); |
25 | | - const dLon = toRad(lon2 - lon1); |
26 | | - const a = |
27 | | - Math.sin(dLat / 2)**2 + |
28 | | - Math.cos(toRad(lat1)) * |
29 | | - Math.cos(toRad(lat2)) * |
30 | | - Math.sin(dLon / 2)**2; |
31 | | - |
32 | | - return 2 * R * Math.asin(Math.sqrt(a)); |
33 | | -} |
34 | | - |
35 | | -function startSimulation() { |
36 | | - const numTrucks = parseInt(document.getElementById("trucks").value); |
37 | | - const numLoads = parseInt(document.getElementById("loads").value); |
38 | | - const minPop = parseInt(document.getElementById("min_pop").value); |
39 | | - |
40 | | - let output = ""; |
41 | | - |
42 | | - //---------------------------------- |
43 | | - // 1. Create trucks |
44 | | - //---------------------------------- |
45 | | - let trucks = []; |
46 | | - for (let i = 0; i < numTrucks; i++) { |
47 | | - let c = randomCity(minPop); |
48 | | - trucks.push({ |
49 | | - id: "T" + (i+1), |
50 | | - city: c.city, |
51 | | - lat: parseFloat(c.lat), |
52 | | - lon: parseFloat(c.lng), |
53 | | - status: "Idle", |
54 | | - capacity: Math.floor(Math.random() * 20) + 10 |
| 1 | +const map = L.map('map').setView([39.5, -98.35], 4); |
| 2 | +L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map); |
| 3 | + |
| 4 | +let truckMarkers = {}; |
| 5 | +let loadLines = {}; |
| 6 | + |
| 7 | +async function refresh() { |
| 8 | + const res = await fetch("http://127.0.0.1:8000/status"); |
| 9 | + const data = await res.json(); |
| 10 | + |
| 11 | + // Trucks |
| 12 | + data.trucks.forEach(truck => { |
| 13 | + if (truckMarkers[truck.truck_id]) { |
| 14 | + truckMarkers[truck.truck_id].setLatLng([truck.lat, truck.lon]); |
| 15 | + truckMarkers[truck.truck_id].bindPopup(`${truck.truck_id} - ${truck.status}`); |
| 16 | + } else { |
| 17 | + truckMarkers[truck.truck_id] = L.circleMarker([truck.lat, truck.lon], { |
| 18 | + radius: 6, |
| 19 | + color: truck.status === "Idle" ? "green" : "orange" |
| 20 | + }).addTo(map).bindPopup(`${truck.truck_id} - ${truck.status}`); |
| 21 | + } |
55 | 22 | }); |
56 | | - } |
57 | | - |
58 | | - //---------------------------------- |
59 | | - // 2. Create loads |
60 | | - //---------------------------------- |
61 | | - let loads = []; |
62 | | - for (let i = 0; i < numLoads; i++) { |
63 | | - let origin = randomCity(minPop); |
64 | | - let dest = randomCity(minPop); |
65 | 23 |
|
66 | | - loads.push({ |
67 | | - id: "L" + (i+1), |
68 | | - origin: origin.city, |
69 | | - dest: dest.city, |
70 | | - latO: parseFloat(origin.lat), |
71 | | - lonO: parseFloat(origin.lng), |
72 | | - latD: parseFloat(dest.lat), |
73 | | - lonD: parseFloat(dest.lng), |
74 | | - weight: Math.floor(Math.random() * 20) + 5 |
| 24 | + // Loads (polylines) |
| 25 | + data.loads.forEach(load => { |
| 26 | + const lineId = load.load_id; |
| 27 | + const origin = [load.latO || load.dest_lat, load.lonO || load.dest_lon]; |
| 28 | + const dest = [load.dest_lat, load.dest_lon]; |
| 29 | + |
| 30 | + if (!loadLines[lineId]) { |
| 31 | + loadLines[lineId] = L.polyline([origin, dest], { |
| 32 | + color: "orange", |
| 33 | + weight: 2, |
| 34 | + dashArray: '5,5' |
| 35 | + }).addTo(map); |
| 36 | + } |
75 | 37 | }); |
76 | | - } |
77 | | - |
78 | | - //---------------------------------- |
79 | | - // 3. Assign loads to trucks |
80 | | - //---------------------------------- |
81 | | - loads.forEach(load => { |
82 | | - let bestTruck = null; |
83 | | - let bestScore = Infinity; |
84 | | - |
85 | | - trucks.forEach(truck => { |
86 | | - if (truck.status === "Idle") { |
87 | | - if (truck.capacity < load.weight) return; |
88 | 38 |
|
89 | | - let dist = haversine(truck.lat, truck.lon, load.latO, load.lonO); |
90 | | - |
91 | | - if (dist < bestScore) { |
92 | | - bestScore = dist; |
93 | | - bestTruck = truck; |
| 39 | + // Remove delivered loads |
| 40 | + Object.keys(loadLines).forEach(lineId => { |
| 41 | + if (!data.loads.some(l => l.load_id === lineId)) { |
| 42 | + map.removeLayer(loadLines[lineId]); |
| 43 | + delete loadLines[lineId]; |
94 | 44 | } |
95 | | - } |
96 | 45 | }); |
| 46 | +} |
97 | 47 |
|
98 | | - if (bestTruck) { |
99 | | - bestTruck.status = "Assigned β " + load.id; |
100 | | - load.assigned = bestTruck.id; |
101 | | - } else { |
102 | | - load.assigned = "NO AVAILABLE TRUCK"; |
103 | | - } |
104 | | - }); |
105 | | - |
106 | | - //---------------------------------- |
107 | | - // Output |
108 | | - //---------------------------------- |
109 | | - output += "π TRUCKS:\n"; |
110 | | - trucks.forEach(t => { |
111 | | - output += `${t.id} β ${t.city} β ${t.status} β cap ${t.capacity}\n`; |
112 | | - }); |
| 48 | +// Initialize simulation |
| 49 | +async function startSimulation() { |
| 50 | + const numTrucks = document.getElementById("numTrucks").value; |
| 51 | + const numLoads = document.getElementById("numLoads").value; |
| 52 | + const minPop = document.getElementById("minPop").value; |
113 | 53 |
|
114 | | - output += "\nπ¦ LOADS:\n"; |
115 | | - loads.forEach(l => { |
116 | | - output += `${l.id} β ${l.origin} β ${l.dest} β weight ${l.weight} β assigned: ${l.assigned}\n`; |
117 | | - }); |
| 54 | + await fetch(`http://127.0.0.1:8000/init?num_trucks=${numTrucks}&num_loads=${numLoads}&min_pop=${minPop}`); |
| 55 | + alert("Simulation started!"); |
| 56 | +} |
118 | 57 |
|
119 | | - document.getElementById("output").innerText = output; |
| 58 | +async function createLoad() { |
| 59 | + await fetch("http://127.0.0.1:8000/create_load"); |
120 | 60 | } |
| 61 | + |
| 62 | +// Auto-refresh every 1 second |
| 63 | +setInterval(refresh, 1000); |
0 commit comments