/ js / transport.js
transport.js
  1  /*global $, L, URI*/
  2  
  3  var parsed;
  4  var globalState = {};
  5  var map;
  6  var sidebar;
  7  var routeLayer;
  8  var tileGroup;
  9  
 10  L.LatLngBounds.prototype.trim = function (precision) {
 11      this._northEast.lat = this._northEast.lat.toFixed(precision);
 12      this._northEast.lng = this._northEast.lng.toFixed(precision);
 13      this._southWest.lat = this._southWest.lat.toFixed(precision);
 14      this._southWest.lng = this._southWest.lng.toFixed(precision);
 15      return this;
 16  };
 17  
 18  L.LatLngBounds.prototype.toXobbString = function () {
 19      // Return bbox string compatible with Overpass API
 20      return this._southWest.lat + "," + this._southWest.lng + "," + this._northEast.lat + "," + this._northEast.lng;
 21  };
 22  
 23  var setTiles = function(tile) {
 24      if (tile) {
 25          localStorage.setItem("otv-tiles", tile);
 26      }
 27      tileGroup.clearLayers();
 28      tileGroup.addLayer(tiles[localStorage.getItem("otv-tiles")]);
 29  }
 30  
 31  var initMap = function() {
 32      map = L.map('map').setView(defaultMapView.coords, defaultMapView.zoom);
 33      tileGroup = L.layerGroup().addTo(map);
 34      setTiles();
 35  
 36      sidebar = L.control.sidebar('sidebar').addTo(map);
 37  
 38      // Ask user location. See map.on('locationfound')
 39      map.locate();
 40  }
 41  
 42  function updateURL() {
 43      var uri = URI();
 44      uri.search(globalState);
 45      history.pushState({globalState: globalState}, null, uri.toString());
 46      $("#dlForm input[type='text']").each(function () {
 47          $(this).val(globalState[$(this).attr("name")]);
 48      });
 49  }
 50  
 51  function bindEvents() {
 52      // To be executed on page load
 53  
 54      var uri = URI();
 55      // Restore global state from URL parameters
 56      globalState = uri.search(true);
 57      if (globalState.bb) {
 58          $("#bb-check").prop('checked', true);
 59      }
 60  
 61      $('#dlForm').submit(function (e) {
 62          e.preventDefault();
 63  
 64          $(this).find("input[type='text']").each(function () {
 65              if ($(this).val()) {
 66                  globalState[$(this).attr("name")] = $(this).val();
 67              } else {
 68                  delete globalState[$(this).attr("name")];
 69              }
 70          });
 71          updateURL();
 72          getRouteMastersByParams(globalState.network, globalState.operator, globalState.ref, globalState.bb);
 73          sidebar.open("data_display");
 74      });
 75  
 76      map.on('locationfound', function (l) {
 77          map.fitBounds(l.bounds, mapPadding);
 78      });
 79  
 80      map.on('moveend', function () {
 81          globalState.lat = map.getCenter().lat.toFixed(5);
 82          globalState.lng = map.getCenter().lng.toFixed(5);
 83          globalState.z   = map.getZoom();
 84          if ($("#bb-check").prop("checked")) {
 85              globalState.bb = map.getBounds().trim(5).toXobbString();
 86          }
 87          updateURL();
 88      });
 89  
 90      $(".otv-settings").on('change', function () {
 91          localStorage.setItem($(this).attr('id'), $(this).val());
 92      });
 93  
 94      $("#bb-check").on('change', function () {
 95          if ($(this).is(':checked')) {
 96              globalState.bb = map.getBounds().trim(5).toXobbString();
 97          } else {
 98              delete globalState.bb;
 99          }
100      });
101  
102      $("#routemaster-tags-toggle").on("click", function () {
103          $("#routemaster-tags").toggle();
104      });
105      $("#route-tags-toggle").on("click", function () {
106          $("#route-tags").toggle();
107      });
108  
109      $("#routemaster-displayAll").on("click", displayAllOnMap);
110  
111      $("#routemaster-select")
112          .removeClass("hidden")
113          .change(function () {
114              globalState.selrm = $(this).val();
115              $("#routemaster-select").val(globalState.selrm);
116              updateStatus("dl");
117              updateURL();
118              getRouteMasterById(globalState.selrm,
119                  function (route_master) {
120                      if (!route_master) {
121                          updateStatus("fail", "No route_master found");
122                      } else {
123                          updateStatus("ok");
124                          displayRoutes(route_master);
125                      }
126                  }, function () {
127                      updateStatus("fail", "Error while getting route_master data");
128                  });
129          });
130      $("#otv-tiles").on("change", function() {
131          setTiles();
132      });
133  }
134  
135  function updateStatus(status, msg) {
136      var level="info";
137  
138      $("li#data_tab i").removeClass().addClass("fa");
139      switch (status) {
140      case "ok":
141          $("li#data_tab i").addClass("fa-bars");
142          $("li#data_tab").removeClass("disabled");
143          break;
144      case "dl":
145          $("li#data_tab i").addClass("fa-spin fa-spinner");
146          $("li#data_tab").addClass("disabled");
147          break;
148      case "fail":
149          level = "warning";
150          $("li#data_tab").removeClass("disabled");
151          $("li#data_tab i").addClass("fa-exclamation-triangle");
152          break;
153      default:
154          break;
155      }
156  
157      var divMessage = "";
158      if (msg) {
159          divMessage = msg;
160      } else if (defaultStatusMessages[status] !== undefined) {
161          divMessage = defaultStatusMessages[status];
162      }
163      if (divMessage.length > 0) {
164          $("#data-status")
165              .removeClass()
166              .addClass(level)
167              .text(divMessage);
168      } else {
169          $("#data-status").empty();
170      }
171  }
172  
173  function guessQuery() {
174      // Find what to do on page opening
175      if (globalState.rmid) {
176          getRouteMasterById(globalState.rmid);
177          sidebar.open("data_display");
178      } else if (globalState.rmid || globalState.network || globalState.operator || globalState.bb) { // Avoid queries which can match too much routes
179          getRouteMastersByParams(globalState.network, globalState.operator, globalState.ref, globalState.bb);
180          sidebar.open("data_display");
181      } else if (! localStorage.getItem("otv-readIntro")){
182          // User has not yet read the help, and haven't made a request or been linked to one
183          localStorage.setItem("otv-readIntro", true);
184          sidebar.open("info-tab");
185      } else {
186          sidebar.open("query");
187      }
188      updateURL();
189  }
190  
191  function displayRouteMasters() {
192      if (!Object.keys(parsed.route_masters).length) {
193          updateStatus("fail", "No route_masters found");
194          return;
195      }
196      updateStatus("ok");
197      $("#routemaster-displayAll").removeClass("hidden");
198      var sorted = _.sortBy(parsed.route_masters, function (e) {return e.tags.name;});
199      $("#routemaster-select").empty();
200      $.each(sorted, function (i, r) {
201          $("#routemaster-select").append($('<option>', {
202              value: r.id,
203              text: r.tags.name || r.id,
204          }));
205      });
206      // Always trigger route variant display
207      if(globalState.selrm) {
208          $("#routemaster-select").val(globalState.selrm);
209      }
210      $("#routemaster-select").change();
211  }
212  
213  function getRouteMastersByParams(network, operator, ref, bbox) {
214      updateStatus("dl");
215  
216  	var netstr = network  ? ("[network~'" + network + "',i]") : "";
217  	var opstr =  operator ? ("[operator~'" + operator + "',i]") : "";
218  	var refstr = ref      ? ("[ref~'^" + ref + "$',i]")   : "";
219  
220      var base;
221      if (bbox) {
222          // Bounding-box filter only works with first-level members of relation
223          // It doesn't recurse into member relations
224          bbox =   bbox ? ("(" + bbox + ")") : "";
225          base = 'relation["type"="route"]' + bbox + ";" +
226          'relation(br)' + netstr + opstr + refstr;
227      } else {
228          base = 'relation["type"="route_master"]' + netstr + opstr + refstr;
229      }
230      getRouteMastersData(base, displayRouteMasters);
231  }
232  
233  function clearMap() {
234      if (map.hasLayer(routeLayer)) {
235          map.removeLayer(routeLayer);
236      }
237      routeLayer = L.layerGroup();
238  }
239  
240  function displayOnMap(route) {
241      _.each(route.stop_positions, function (obj) {
242          makeMarker(obj, routeLayer);
243      });
244      _.each(route.platforms, function (obj) {
245          makeMarker(obj, routeLayer);
246      });
247      _.each(route.paths, function (obj) {
248          makeMarker(obj, routeLayer, {
249              color: route.tags.colour || path_color[route.tags.route] || "red",
250  
251          });
252      });
253      if(routeLayer.getLayers().length > 0) {
254          if(window.innerWidth > mapPadding["paddingTopLeft"][0]) {
255              map.fitBounds(L.featureGroup(routeLayer.getLayers()).getBounds(), mapPadding);
256          } else {
257              map.fitBounds(L.featureGroup(routeLayer.getLayers()).getBounds());
258          }
259          routeLayer.addTo(map);
260      }
261  }
262  
263  function getTagTable(obj) {
264      var tagStr = "<table class='tags'>";
265      Object.keys(obj.tags).forEach(function (key) {
266          tagStr += `<tr><td class='key'>${key}</td><td class='value'>${obj.tags[key].autoLink()}</td></tr>`;
267      });
268      tagStr += "</table>";
269      return tagStr;
270  }
271  
272  function getLatLngArray(osmWay) {
273          var latlngs = [];
274          _.each(osmWay.nodes, function (n) {
275              latlngs.push(L.latLng(n.lat, n.lon));
276          });
277          return latlngs;
278  }
279  
280  function makeMarker(obj, group, overrideStyle) {
281      var markerOptions = {
282          autoPan: false
283      };
284      var popupHTML = `<a href='${osmUrl}${obj.type}/${obj.id}'><h1>${obj.tags.name || "!Missing name!"}</h1></a>
285          ${getTagTable(obj)}`;
286      if (obj.type == "way") {
287          var latlngs = getLatLngArray(obj);
288          if (obj.tags.public_transport === "platform") {
289              obj.layer = L.polyline(latlngs,{
290                  color: 'blue',
291                  weight: 8
292              }).bindPopup(popupHTML, markerOptions);
293          }
294          else {
295              obj.layer = L.polyline(latlngs,$.extend({
296                  weight: 4
297              }, overrideStyle)).bindPopup(popupHTML, markerOptions);
298         }
299      } else {
300          if (obj.tags.public_transport === "stop_position") {
301              obj.layer = L.marker([obj.lat, obj.lon], {
302                  icon: iconStopPosition
303              }).bindPopup(popupHTML, markerOptions);
304          } else if (obj.tags.public_transport === "platform") {
305              obj.layer = L.marker([obj.lat, obj.lon], {
306                  icon: platformIcon
307              }).bindPopup(popupHTML, markerOptions);
308          } else {
309              obj.layer = L.marker([obj.lat, obj.lon])
310              .bindPopup(popupHTML, markerOptions);
311          }
312      }
313      group.addLayer(obj.layer);
314  }
315  
316  function displayRoutes(route_master) {
317      //Display informations relative to the route_master chosen
318  
319      $("#routemaster-tags-toggle").removeClass("hidden");
320      $("#routemaster-name").text(route_master.tags.name);
321      $("#routemaster-tags").html(getTagTable(route_master));
322  
323      // Clear data display before displaying new route variants
324      $("#routes_list ul").empty();
325      $('#stops-list').find("li").remove();
326  
327      _.each(route_master.members, function (r) {
328          var routeLi = $("<li>").addClass(r.tags.route + "_route");
329  
330          $("<a>", {href: osmUrl + "relation" + "/" + r.id})
331              .attr("target","_blank")
332              .append($("<img>", {src: route_icons[r.tags.route], alt: "route on osm.org"}))
333              .appendTo(routeLi);
334  
335          $("<span>")
336              .text(r.tags.name)
337              .prop("title", r.tags.name)
338              .attr("data-osmid", r.id)
339              .on("click", function () {
340                  $("#routes_list>ul>li>span").removeClass("selected_route");
341                  $(this).addClass("selected_route");
342                  updateURL();
343                  displayRouteData(parsed.routes[$(this).data("osmid")]);
344                  clearMap();
345                  displayOnMap(parsed.routes[$(this).data("osmid")]);
346              })
347              .appendTo(routeLi);
348  
349          $("#routes_list>ul").append(routeLi);
350      });
351      // Display the first route variant if it exists
352      if(route_master.members[0]) {
353          var id = route_master.members[0].id;
354          $(`span[data-osmID=${id}]`).addClass("selected_route");
355          clearMap();
356          displayRouteData(parsed.routes[id]);
357          displayOnMap(parsed.routes[id]);
358      }
359  }
360  
361  function displayAllOnMap() {
362      clearMap();
363      _.each(parsed.routes, displayOnMap);
364  }
365  
366  function displayRouteData(route) {
367      // Display route tags
368      $("#route-tags").html(getTagTable(route));
369      $("#route-tags-toggle").show();
370  
371      // Clear data display before new display
372      $('#stops-list').find("li").remove();
373      var master_li;
374      _.each(route.members, function (member) {
375          if (!member.role.match(/stop(_entry_only|_exit_only)?/)) {
376              return;
377          }
378          master_li = $("<li>");
379          stop_ul = $("<ul>");
380          if (member.stop_area) {
381              $("<a>", {href: osmUrl + "relation/" + member.stop_area.id})
382                  .append($("<img>", {src: "img/relation.svg", alt: "stop_area relation"}))
383                  .appendTo(master_li);
384              $("<span>").html(member.stop_area.tags.name || member.stop_area.id)
385                  .addClass("route_master-name")
386                  .appendTo(master_li);
387          } else {
388              $("<span>")
389                  .text("Missing stop_area relation")
390                  .addClass("route_master-name")
391                  .appendTo(master_li);
392          }
393          stop_li = $("<li>");
394          $("<a>", {href: osmUrl + member.type + "/" + member.id, "data-osm": member.id})
395              .append($("<img>", {src: "img/stop_position_32.png", alt: "Stop_position"}))
396              .appendTo(stop_li);
397          $("<span>")
398              .text("♿")
399              .addClass("wheelchair feature_" + member.tags.wheelchair)
400              .appendTo(stop_li);
401          $("<span>").html(member.tags.name || member.id)
402              .appendTo(stop_li);
403          stop_li.on("click", null, member, function () {
404              member.layer.openPopup();
405          })
406          .on("mouseleave", null, member, function () {
407              member.layer.closePopup();
408          });
409          stop_ul.append(stop_li);
410  
411          var platforms = findPlatforms(route, member.stop_area);
412          _.each(platforms, function (platform) {
413              var platform_li = $("<li>");
414              $("<a>", {href: osmUrl + platform.type + "/" + platform.id})
415                  .append($("<img>", {src: "img/platform_14.png", alt: "Platform"}))
416                  .appendTo(platform_li);
417              $("<span>")
418                  .text("♿")
419                  .addClass("wheelchair feature_" + platform.tags.wheelchair)
420                  .appendTo(platform_li);
421              $("<span>")
422                  .text(platform.tags.name || platform.id)
423                  .appendTo(platform_li);
424  
425              platform_li.on("click", null, member, function () {
426                  platform.layer.openPopup();
427              })
428              .on("mouseleave", null, platform, function () {
429                  platform.layer.closePopup();
430              })
431              .appendTo(stop_ul);
432          });
433          master_li.append(stop_ul);
434          $('#stops-list').append(master_li);
435      });
436  }
437  
438  function findPlatforms(route, stop_area) {
439  	if (!stop_area) {
440  		return [];
441      }
442      platform_regex = new RegExp("platform(_entry_only|_exit_only)?");
443      var route_platforms = _.filter(route.members, function (p){return platform_regex.test(p.role);});
444      var area_platforms = _.filter(stop_area.members, function (p){return platform_regex.test(p.role);});
445      return _.intersection(route_platforms, area_platforms);
446  }
447  
448  function initOptions() {
449      for (var o in defaultOptions) {
450          if (defaultOptions.hasOwnProperty(o) && !localStorage.getItem(o)) {
451              localStorage.setItem(o, defaultOptions[o]);
452          }
453      }
454      $(".otv-settings").each(function () {
455          $(this).val(localStorage.getItem($(this).attr('id')));
456      });
457  }
458  
459  initOptions();
460  initMap();
461  bindEvents();
462  guessQuery();