1/* global window, document, $, mapboxgl, _ */ 2/* eslint no-var: "off", prefer-arrow-callback: "off", no-unused-vars: "off" */ 3 4mapboxgl.accessToken = 'pk.eyJ1IjoibXZnbyIsImEiOiJjazR5cHo2ZTkwMzVpM25xcmlldjVueHk5In0.6Hy1Jf4d1r0OghIg3PLB6Q'; 5 6function formatPopup(features) { 7 var html = ''; 8 9 var routes = _.groupBy(features, function(feature) { 10 return feature.properties.route_long_name; 11 }); 12 13 if (_.size(routes) > 1) { 14 html += '<div class="popup-title">Routes</div>'; 15 } 16 17 _.each(routes, function(routeFeatures) { 18 if (routeFeatures[0].properties.route_url) { 19 html += '<div><a href="' + routeFeatures[0].properties.route_url + '">' + routeFeatures[0].properties.route_long_name + '</a></div>'; 20 } else { 21 html += '<div>' + routeFeatures[0].properties.route_long_name + '</div>'; 22 } 23 }); 24 25 return html; 26} 27 28jQuery(function($) { 29 function formatRoute(route) { 30 const html = route.route_url ? $('<a>').attr('href', route.route_url) : $('<div>'); 31 32 html.addClass('route-item text-sm'); 33 34 if (route.route_color) { 35 $('<div>') 36 .addClass('route-color-swatch') 37 .css('backgroundColor', route.route_color) 38 .appendTo(html); 39 } 40 41 $('<span>') 42 .text(`${route.route_short_name} ${route.route_long_name}`) 43 .appendTo(html); 44 45 return html.prop('outerHTML'); 46 } 47 48 function formatRoutePopup(features) { 49 const html = $('<div>'); 50 51 if (features.length > 1) { 52 $('<div>') 53 .addClass('popup-title') 54 .text('Routes') 55 .appendTo(html); 56 } 57 58 $(html).append(features.map(feature => formatRoute(feature.properties))); 59 60 return html.prop('outerHTML'); 61 } 62 63 function formatPointPopup(feature) { 64 const routes = JSON.parse(feature.properties.routes); 65 const html = $('<div>'); 66 67 $('<div>') 68 .addClass('popup-title') 69 .text(feature.properties.stop_name) 70 .appendTo(html); 71 72 $('<div>') 73 .text('Routes Served:') 74 .appendTo(html); 75 76 $(html).append(routes.map(route => formatRoute(route))); 77 78 return html.prop('outerHTML'); 79 } 80 81 const map = new mapboxgl.Map({ 82 container: 'service-map', 83 style: 'mapbox://styles/mvgo/ckm5zwnpe1y6217lel2qoyzca', 84 center: [-122.0809, 37.4091], 85 zoom: 11.7 86 }); 87 88 map.addControl(new mapboxgl.NavigationControl()); 89 90 map.on('style.load', function () { 91 map.scrollZoom.disable(); 92 93 map.addSource('routes', { 94 type: 'geojson', 95 data: '/data/mvgo.geojson' 96 }); 97 98 const layers = map.getStyle().layers; 99 // Find the index of the first symbol layer in the map style 100 let firstSymbolId; 101 for (const layer of layers) { 102 if (layer.type === 'symbol') { 103 firstSymbolId = layer.id; 104 break; 105 } 106 } 107 108 // Add white outlines to routes first 109 map.addLayer({ 110 'id': 'route-outlines', 111 'type': 'line', 112 'source': 'routes', 113 'paint': { 114 'line-color': '#FFFFFF', 115 'line-opacity': 1, 116 'line-width': 8 117 }, 118 layout: { 119 'line-join': 'round', 120 'line-cap': 'round' 121 }, 122 'filter': ['has', 'route_id'] 123 }, firstSymbolId); 124 125 // Add route lines next 126 map.addLayer({ 127 'id': 'routes', 128 'type': 'line', 129 'source': 'routes', 130 'paint': { 131 'line-color': [ 132 'get', 133 'route_color' 134 ], 135 'line-opacity': 1, 136 'line-width': 4 137 }, 138 layout: { 139 'line-join': 'round', 140 'line-cap': 'round' 141 }, 142 'filter': ['has', 'route_id'] 143 }, firstSymbolId); 144 145 146 // Add highlighted route lines next 147 map.addLayer({ 148 'id': 'highlighted-routes', 149 'type': 'line', 150 'source': 'routes', 151 'paint': { 152 'line-color': [ 153 'get', 154 'route_color' 155 ], 156 'line-opacity': 1, 157 'line-width': 6 158 }, 159 layout: { 160 'line-join': 'round', 161 'line-cap': 'round' 162 }, 163 'filter': ['==', ['get', 'route_short_name'], 'none'] 164 }, firstSymbolId); 165 166 map.addLayer({ 167 'id': 'stops', 168 'type': 'circle', 169 'source': 'routes', 170 'paint': { 171 'circle-radius': { 172 stops: [ 173 [12, 0], 174 [13, 4], 175 [15, 6] 176 ] 177 }, 178 'circle-stroke-width': 1, 179 'circle-stroke-color': '#363636', 180 'circle-stroke-opacity': { 181 stops: [ 182 [12, 0], 183 [13, 1] 184 ] 185 }, 186 'circle-color': '#363636', 187 'circle-opacity': { 188 stops: [ 189 [12, 0], 190 [13, 1] 191 ] 192 } 193 }, 194 'filter': ['has', 'stop_id'] 195 }, firstSymbolId); 196 197 // Add labels 198 map.addLayer({ 199 'id': 'route-labels', 200 'type': 'symbol', 201 'source': 'routes', 202 'layout': { 203 'symbol-placement': 'line', 204 'text-field': ['get', 'route_short_name'], 205 'text-size': 14 206 }, 207 'paint': { 208 'text-color': '#000000', 209 'text-halo-width': 2, 210 'text-halo-color': '#ffffff' 211 }, 212 'filter': ['==', ['get', 'route_short_name'], 'none'] 213 }); 214 215 let mapWasLoaded = false;
216 map.on('render', () => { 217 if (!map.loaded() || mapWasLoaded) { 218 return; 219 } 220 221 const features = map.querySourceFeatures('routes'); 222 223 const bounds = new mapboxgl.LngLatBounds(); 224 225 for (const feature of features) { 226 if (feature.geometry.type === 'MultiLineString') { 227 for (const lineString of feature.geometry.coordinates) { 228 for (const coordinate of lineString) { 229 bounds.extend(coordinate); 230 } 231 } 232 } else if (feature.geometry.type === 'LineString') { 233 for (const coordinate of feature.geometry.coordinates) { 234 bounds.extend(coordinate); 235 } 236 } 237 } 238 239 map.fitBounds(bounds, { padding: 30 }) 240 mapWasLoaded = true; 241 }); 242 243 map.on('mouseenter', 'stops', function () { 244 map.getCanvas().style.cursor = 'pointer'; 245 }); 246 247 map.on('mouseleave', 'stops', function () { 248 map.getCanvas().style.cursor = ''; 249 }); 250 251 map.on('mouseenter', 'routes', function () { 252 map.getCanvas().style.cursor = 'pointer'; 253 }); 254 255 map.on('mouseleave', 'routes', function () { 256 map.getCanvas().style.cursor = ''; 257 }); 258 259 map.on('mouseenter', 'route-outlines', function () { 260 map.getCanvas().style.cursor = 'pointer'; 261 }); 262 263 map.on('mouseleave', 'route-outlines', function () { 264 map.getCanvas().style.cursor = ''; 265 }); 266 267 map.on('click', function (e) { 268 // set bbox as 5px reactangle area around clicked point 269 const bbox = [ 270 [e.point.x - 5, e.point.y - 5], 271 [e.point.x + 5, e.point.y + 5] 272 ]; 273 274 // Check for stops clicked 275 if (map.getZoom() >= 12) { 276 const stopFeatures = map.queryRenderedFeatures(bbox, { 277 layers: ['stops'] 278 }); 279 if (stopFeatures.length > 0) { 280 new mapboxgl.Popup() 281 .setLngLat(e.lngLat) 282 .setHTML(formatPointPopup(stopFeatures[0])) 283 .addTo(map); 284 return; 285 } 286 } 287 288 const lineFeatures = map.queryRenderedFeatures(bbox, { 289 layers: ['routes', 'route-outlines'] 290 }); 291 292 if (lineFeatures.length > 0) { 293 const routeFeatures = _.uniq(lineFeatures, function (feature) { 294 return feature.properties.route_short_name; 295 }); 296 297 return new mapboxgl.Popup() 298 .setLngLat(e.lngLat) 299 .setHTML(formatRoutePopup(routeFeatures)) 300 .addTo(map); 301 } 302 }); 303 }); 304});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.