|
| 1 | +const mockVehicles = [ |
| 2 | + { |
| 3 | + id: "SEA-101", |
| 4 | + label: "Bus 101", |
| 5 | + lat: 47.6101, |
| 6 | + lng: -122.3426, |
| 7 | + route: "Rapid E Line", |
| 8 | + status: "active", |
| 9 | + corridor: "Downtown to Ballard", |
| 10 | + stop: "3rd Ave & Pine St", |
| 11 | + }, |
| 12 | + { |
| 13 | + id: "SEA-108", |
| 14 | + label: "Bus 108", |
| 15 | + lat: 47.6206, |
| 16 | + lng: -122.3201, |
| 17 | + route: "Route 8", |
| 18 | + status: "active", |
| 19 | + corridor: "Capitol Hill Crosstown", |
| 20 | + stop: "Denny Way & Broadway", |
| 21 | + }, |
| 22 | + { |
| 23 | + id: "SEA-214", |
| 24 | + label: "Bus 214", |
| 25 | + lat: 47.5989, |
| 26 | + lng: -122.3347, |
| 27 | + route: "South Lake Loop", |
| 28 | + status: "active", |
| 29 | + corridor: "Pioneer Square Connector", |
| 30 | + stop: "Jackson St Transit Hub", |
| 31 | + }, |
| 32 | + { |
| 33 | + id: "SEA-305", |
| 34 | + label: "Bus 305", |
| 35 | + lat: 47.6677, |
| 36 | + lng: -122.3826, |
| 37 | + route: "Rapid E Line", |
| 38 | + status: "idle", |
| 39 | + corridor: "Northwest Layover", |
| 40 | + stop: "Ballard Ave NW", |
| 41 | + }, |
| 42 | + { |
| 43 | + id: "SEA-417", |
| 44 | + label: "Bus 417", |
| 45 | + lat: 47.6267, |
| 46 | + lng: -122.3561, |
| 47 | + route: "South Lake Loop", |
| 48 | + status: "active", |
| 49 | + corridor: "Seattle Center Spur", |
| 50 | + stop: "Queen Anne Ave N", |
| 51 | + }, |
| 52 | + { |
| 53 | + id: "SEA-522", |
| 54 | + label: "Bus 522", |
| 55 | + lat: 47.5884, |
| 56 | + lng: -122.3023, |
| 57 | + route: "Route 8", |
| 58 | + status: "idle", |
| 59 | + corridor: "Mount Baker Relief", |
| 60 | + stop: "Rainier Ave S", |
| 61 | + }, |
| 62 | +]; |
| 63 | + |
| 64 | +const mockCorridors = [ |
| 65 | + { |
| 66 | + name: "Rapid E Line", |
| 67 | + color: "#0f766e", |
| 68 | + points: [ |
| 69 | + [47.6101, -122.3426], |
| 70 | + [47.6205, -122.3492], |
| 71 | + [47.6362, -122.3563], |
| 72 | + [47.6516, -122.3752], |
| 73 | + [47.6677, -122.3826], |
| 74 | + ], |
| 75 | + }, |
| 76 | + { |
| 77 | + name: "Route 8", |
| 78 | + color: "#f59e0b", |
| 79 | + points: [ |
| 80 | + [47.5884, -122.3023], |
| 81 | + [47.6002, -122.3119], |
| 82 | + [47.6117, -122.3174], |
| 83 | + [47.6206, -122.3201], |
| 84 | + [47.6312, -122.3225], |
| 85 | + ], |
| 86 | + }, |
| 87 | + { |
| 88 | + name: "South Lake Loop", |
| 89 | + color: "#0284c7", |
| 90 | + points: [ |
| 91 | + [47.5989, -122.3347], |
| 92 | + [47.6072, -122.3324], |
| 93 | + [47.6202, -122.3384], |
| 94 | + [47.6267, -122.3561], |
| 95 | + ], |
| 96 | + }, |
| 97 | +]; |
| 98 | + |
| 99 | +function makeMarkerIcon(status) { |
| 100 | + const markerClass = status === "active" ? "bus-marker bus-marker--active" : "bus-marker bus-marker--idle"; |
| 101 | + return L.divIcon({ |
| 102 | + className: "", |
| 103 | + html: `<div class="${markerClass}"> |
| 104 | + <span class="bus-marker__pulse"></span> |
| 105 | + <span class="bus-marker__icon">🚌</span> |
| 106 | + </div>`, |
| 107 | + iconSize: [42, 42], |
| 108 | + iconAnchor: [21, 21], |
| 109 | + popupAnchor: [0, -18], |
| 110 | + }); |
| 111 | +} |
| 112 | + |
| 113 | +function buildPopup(vehicle) { |
| 114 | + const badgeClass = vehicle.status === "active" |
| 115 | + ? "map-popup__badge map-popup__badge--active" |
| 116 | + : "map-popup__badge map-popup__badge--idle"; |
| 117 | + |
| 118 | + return `<div class="map-popup"> |
| 119 | + <div class="map-popup__header"> |
| 120 | + <div> |
| 121 | + <div class="map-popup__title">🚌 ${vehicle.label}</div> |
| 122 | + <div style="font-size:12px;color:#64748b;margin-top:2px;">${vehicle.id}</div> |
| 123 | + </div> |
| 124 | + <span class="${badgeClass}">${vehicle.status}</span> |
| 125 | + </div> |
| 126 | + <div class="map-popup__meta"> |
| 127 | + <div><strong>Route</strong> ${vehicle.route}</div> |
| 128 | + <div><strong>Corridor</strong> ${vehicle.corridor}</div> |
| 129 | + <div><strong>Nearest stop</strong> ${vehicle.stop}</div> |
| 130 | + </div> |
| 131 | + </div>`; |
| 132 | +} |
| 133 | + |
| 134 | +function initMap() { |
| 135 | + const el = document.getElementById("main-map"); |
| 136 | + if (!el) return; |
| 137 | + const map = L.map("main-map", { |
| 138 | + zoomControl: false, |
| 139 | + scrollWheelZoom: true, |
| 140 | + }).setView([47.6062, -122.3321], 13); |
| 141 | + |
| 142 | + L.tileLayer("https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png", { |
| 143 | + attribution: "© OpenStreetMap contributors © CARTO", |
| 144 | + maxZoom: 19, |
| 145 | + }).addTo(map); |
| 146 | + |
| 147 | + L.control.zoom({ position: "bottomright" }).addTo(map); |
| 148 | + |
| 149 | + mockCorridors.forEach(corridor => { |
| 150 | + L.polyline(corridor.points, { |
| 151 | + color: corridor.color, |
| 152 | + weight: 5, |
| 153 | + opacity: 0.82, |
| 154 | + lineCap: "round", |
| 155 | + }) |
| 156 | + .addTo(map) |
| 157 | + .bindTooltip(corridor.name, { |
| 158 | + direction: "top", |
| 159 | + offset: [0, -4], |
| 160 | + opacity: 0.95, |
| 161 | + }); |
| 162 | + }); |
| 163 | + |
| 164 | + mockVehicles.forEach(v => { |
| 165 | + L.marker([v.lat, v.lng], { icon: makeMarkerIcon(v.status) }) |
| 166 | + .addTo(map) |
| 167 | + .bindPopup(buildPopup(v)); |
| 168 | + }); |
| 169 | + |
| 170 | + const activeCount = mockVehicles.filter(vehicle => vehicle.status === "active").length; |
| 171 | + const activeCountEl = document.getElementById("fleet-active-count"); |
| 172 | + const routeCountEl = document.getElementById("route-count"); |
| 173 | + |
| 174 | + if (activeCountEl) activeCountEl.textContent = String(activeCount); |
| 175 | + if (routeCountEl) routeCountEl.textContent = String(mockCorridors.length); |
| 176 | +} |
| 177 | + |
| 178 | +// Auto-initialize the map if this page has the map container |
| 179 | +if (document.getElementById("main-map")) { |
| 180 | + initMap(); |
| 181 | +} |
0 commit comments