PageSourceSearch

https://mvgo.org/wp-content/themes/mvgo/js/system-map.js?ver=1.0.12

js mvgo.org collected 2026-10-03 23:40:24 UTC 7,615 bytes, 304 lines download raw bytes

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.