/** * @file ouestmonbus.com main source file. * * @copyright Benoît Meunier & Karin Meunier - 2015-2023 * * @license * ouestmonbus.com is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ouestmonbus.com is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ouestmonbus.com. If not, see . */ "use strict"; const STATION_CONTENT_REFRESH = 2 * 60 * 1000; const STATION_LIST_REFRESH = 4 * 60 * 1000; const version = '%VERSION%'; const date_version = '%DATEVERSION%'; const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; const apikey = "3324de385f6b7b5bdfa1727d3e1a9f4bed5dd1accb288db6df5e8487"; log.setLevel(1); /////////////////////////////////////////////////////////////////////////////// ///////////////////////// Classe OuestmonbusApp//////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /** * Classe principale de l'application. * * @constructor */ function OuestmonbusApp() { this.localstoragePermitted = false; try { localStorage.setItem("localStorage", 1); localStorage.removeItem("localStorage"); this.localstoragePermitted = true; } catch (e) {} // Carte Leaflet this.map = null; // Classe de gestion de la localisation this.localisation = null; // Classe de gestion des info traffic (dans ce fichier) this.infostrafics = null; this.needHelp = this.getLocalStorage("alreadyDisplayHelp") === null; this.firstdisplayRetardHelp = this.getLocalStorage("alreadyDisplayRetardHelp") === null; this.firstdisplayNoBusHelp = this.getLocalStorage("alreadyDisplayNoBusHelp") === null; this.allStationsDataPath = this.getLocalStorage("allStationsDataPath"); this.allStationsDataCache = JSON.parse(this.getLocalStorage("allStationsDataCache")); this.refreshAllStationsTimer = null; this.refreshStationTimer = null; this.firstStationDisplaySession = true; this.stationFoundInCenter = null; this.markerCurrentDisplay = null; this.dialogCurrentDisplay = null; this.lines_layers = []; this.bus_group_layer = L.layerGroup(); this.stations_on_active_line_layer_group = L.layerGroup(); this.bus_station_origin = null; this.last_lignes_selected = []; this.last_ligne_bus_fetch = null; this.waitSpinner = null; this.waitSpinnerDisplayed = false; this.progressbar = null; this.hash_change_from_move = false; this.md = new MobileDetect(window.navigator.userAgent); } OuestmonbusApp.prototype.getLocalStorage = function(key) { if (this.localstoragePermitted) { return localStorage.getItem(key); } else { return null; } }; OuestmonbusApp.prototype.setLocalStorage = function(key, value) { if (this.localstoragePermitted) { try { localStorage.setItem(key, value); } catch (e) {} } }; /** * Initialise l'application. */ OuestmonbusApp.prototype.init = function() { var hpg = 0.5; if (this.md.phone()) { hpg = 2; } this.progressbar = new ProgressBar.Line("#progressbar", { duration: STATION_CONTENT_REFRESH, strokeWidth: hpg }); this.progressbar.set(0); $(window).resize(this.doOnResize.bind(this)); this.initTexts(); this.initMap(); this.localisation = new Localization(this, this.map); this.infostrafics = new InfosTrafics(this); this.initMenuButtons(); this.infostrafics.fetchLinesAndAlerts(); this.initPDFLinks(); window.onload = function() { this.start(); }.bind(this); window.onhashchange = function() { this.onHashChange(); }.bind(this); $(".app-container").show(); }; /** * Point d'entrée, lorsque la fenêtre est chargé on exécute le programme. */ OuestmonbusApp.prototype.start = function() { setTimeout(function() { this.showHelp(); }.bind(this), 60000); var display = function() { this.getAllTodayStationsData(this.displayAllStations.bind(this)); }; var zoomLatLng = this.localisation.urlToZoomLatLng(window.location.hash); if (zoomLatLng) { this.moveMapTo(zoomLatLng[1], zoomLatLng[0], display.bind(this)); } else { var last_zoom = this.getLocalStorage("last_zoom"); var last_lat = this.getLocalStorage("last_lat"); var last_lng = this.getLocalStorage("last_lng"); if (last_zoom !== null && last_lat !== null && last_lng !== null) { this.moveMapTo(L.latLng(last_lat, last_lng), last_zoom, display.bind(this)); } else { this.moveMapTo(this.map.DEFAULT_POSITION, this.map.DEFAULT_ZOOM, display.bind(this)); } } }; OuestmonbusApp.prototype.isMapHere = function(latlng, zoom) { var res = false; try { res = this.map.getCenter().distanceTo(latlng) < this.map.DEFAULT_DISTANCE_RESOLUTION && this.map.getZoom() === zoom; } catch (e) {} return res; }; OuestmonbusApp.prototype.moveMapTo = function(latlng, zoom, onFinish) { if (typeof onFinish === "undefined") { onFinish = function() {}; } if (!this.isMapHere(latlng, zoom)) { this.map.once("moveend", function() { onFinish(); }); this.map.setView(latlng, zoom); } else { onFinish(); } }; OuestmonbusApp.prototype.doOnResize = function() { this.placeLegal(); }; /** * Appelé quand l'URL change. */ OuestmonbusApp.prototype.onHashChange = function() { if (!this.hash_change_from_move) { var zoomLatLng = this.localisation.urlToZoomLatLng(window.location.hash); if (zoomLatLng) { this.moveMapTo(zoomLatLng[1], zoomLatLng[0]); } } this.hash_change_from_move = false; }; /** Ajuste la position des éléments Les mentions légal dans div.leaflet-bottom ne sont pas positionné en bas sans un ajustement manuel. */ OuestmonbusApp.prototype.placeLegal = function() { // hauteur des mentions légal var legal_attribution_height = $("#legal_attribution").height(); // taille du menu var appbar_height = $(".app-bar").height(); // taille de la barre de progression var progress_height = $("#progressbar").height(); // taille de la fenêtre du navigateur var window_height = $(window).height(); // position des mentions légale var bottom_position = window_height - legal_attribution_height - progress_height; $("div.leaflet-bottom").css("top", `${bottom_position}px`); $("div.leaflet-top").css("top", `${appbar_height}px`); // taille de la carte var map_height = window_height - progress_height; $("#map").css("height", `${map_height}px`); }; /** Ajuste la position des éléments */ OuestmonbusApp.prototype.addWaitCenterSpinner = function() { if (this.waitSpinner !== null && !this.waitSpinnerDisplayed) { this.waitSpinner.addTo(this.map); this.waitSpinnerDisplayed = true; this.waitSpinner.setPosition("topright"); var spinner_size_px = 52; var padding_right = Math.round(Number($(window).width() / 2 - spinner_size_px / 2)); var padding_top = Math.round(Number($(window).height() / 2 - spinner_size_px / 2 - $(".app-bar").height())); $("div.wait").css("padding-top", padding_top); $("div.wait").css("padding-right", padding_right); } }; OuestmonbusApp.prototype.removeWaitCenterSpinner = function() { if (this.waitSpinner !== null && this.waitSpinnerDisplayed) { this.waitSpinner.remove(this.map); this.waitSpinnerDisplayed = false; } }; /** * Ajuste la position des dialogues un peu plus en hauteur */ OuestmonbusApp.prototype.updateDialogPosition = function(element_id) { $(element_id).css("top", $(".app-bar").height() + Math.ceil(($(window).height() - $(".app-bar").height()) * 0.05)); }; /** * Initialise les textes */ OuestmonbusApp.prototype.initTexts = function() { $("#version").html(`Version ${version} (${date_version})`); $("#app_news").html(marked.parse(` **03/10/2022:** - Correction de bug: les lignes du métro sont maintenant visibles. **28/09/20022:** - Harmonisation des couleurs et du design. - Ajout des nouveautés. **27/09/2022:** - Correction du problème avec le fuseau horaire de l'API Star. - Suppression des widgets Twitter (privacy et performances). **26/09/2022:** - Hébergement: suppression du CDN cloudflare (privacy). - Mise à jours de la carte OpenStreetMap. `)); $("#text_usage").html(marked.parse(`[ouestmonbus.com](https://ouestmonbus.com) est une carte interactive qui vous permet de consulter les prochains passages des bus de Rennes Métropole à leurs arrêts, de visualiser les lignes et d'observer la position des bus en temps réel.`)); $("#text_who_develop").html(marked.parse(` [ouestmonbus.com](https://ouestmonbus.com) est développé par [Benoît Meunier](mailto:contact@ouestmonbus.com) avec la participation de Karin Meunier (directrice graphisme et expérience utilisateur). Ce service est fourni gratuitement sans garantie de fonctionnement. Il est construit à l'aide des librairies [Leaflet](http://leafletjs.com/), [Metro UI CSS](https://metroui.org.ua/), [jQuery](https://jquery.com/), [mobile-detect.js](http://hgoebl.github.io/mobile-detect.js/), [underscore.js](https://underscorejs.org/) et [Moment.js](http://momentjs.com/). L'application est basée sur des données ouvertes (Open Data). Elles proviennent des [contributeurs de OpenStreetMap](http://osm.org/copyright) et des informations du [service de Rennes Métropole et Keolis Rennes](https://data.explore.star.fr/). Cette application web existe depuis 2015, c'est un logiciel libre sous [licence GPL v3](https://ouestmonbus.com/LICENSE), les sources sont [disponibles ici](https://ouestmonbus.com/ouestmonbus_src.zip). `)); } /** * Affichage de la carte. */ OuestmonbusApp.prototype.initMap = function() { // Création de la carte var southWest = L.latLng(47.9690, -2.0316); var northEast = L.latLng(48.3186, -1.3788); var maxBounds = L.latLngBounds(southWest, northEast); this.map = L.map("map", { attributionControl: false, maxBounds: maxBounds, minZoom: 12, zoomControl: true, photonControl: true, photonControlOptions: { placeholder: "Entrez un lieu...", position: "topleft", noResultLabel: "Pas de résultat", feedbackEmail: null, onSelected: function(feature) { this.map.setView([feature.geometry.coordinates[1], feature.geometry.coordinates[0]], 16); $(".photon-input").hide(); }.bind(this) } }); this.map.setView([48.1178, -1.67036], 16); this.map.zoomControl.setPosition('topright'); //#map=16/48.1113/-1.67566 this.map.DEFAULT_ZOOM = 17; this.map.DEFAULT_DISTANCE_RESOLUTION = 1; this.map.DEFAULT_POSITION = L.latLng(48.1178, -1.67036); this.map.MAXBOUNDS = maxBounds; // Ajout des tuiles var tiles_layer = L.tileLayer("https://ouestmonbus.com/tiles/{z}/{x}/{y}.png", { maxZoom: 18, bounds: maxBounds, opacity: 0.75 }); this.map.addLayer(tiles_layer); // Spinner d'attente de chargement des données this.waitSpinner = L.control(); this.waitSpinner.onAdd = function() { this._div = L.DomUtil.create("div", "wait"); this.update(); return this._div; }; this.waitSpinner.update = function() { this._div.innerHTML = "
"; }; // Ajout des clusters de marker this.map.cluster_markers = new L.markerClusterGroup({ disableClusteringAtZoom: this.map.DEFAULT_ZOOM, spiderfyOnMaxZoom: false, maxClusterRadius: 200 }); // ajout du layer de bus this.bus_group_layer.addTo(this.map); // ajout du layer des stations du parcours this.stations_on_active_line_layer_group.addTo(this.map); // Ajout des mentions légals var legal = L.control.attribution({ prefix: ""); legal.setPosition("bottomright"); legal.addTo(this.map); // Force un réalignement this.placeLegal(); this.resetMapListener(); }; OuestmonbusApp.prototype.resetMapListener = function() { this.map.on("moveend", this.onMapMoveEnd.bind(this)); this.map.on("zoomend", this.onMapZoomEnd.bind(this)); }; OuestmonbusApp.prototype.onMapZoomEnd = function() { if (this.stations_on_active_line_layer_group.getLayers().length > 0) { var currentZoom = this.map.getZoom(); var icon_size = 22; var icon_anchor = 11; if (currentZoom < 16) { icon_size = currentZoom; icon_anchor = Math.floor(currentZoom / 2); } var iconSize = [icon_size, icon_size]; var iconAnchor = [icon_anchor, icon_anchor]; this.stations_on_active_line_layer_group.eachLayer(function(layers) { layers.bringToBack(); layers.eachLayer(function(layer) { var icon_option = layer.getIcon(); icon_option["options"]["iconSize"] = icon_size; icon_option["options"]["iconAnchor"] = icon_anchor; layer.setIcon(icon_option); }); }); } }; OuestmonbusApp.prototype.onMapMoveEnd = function() { this.hideDlg(true); this.hash_change_from_move = true; var pos = this.map.getCenter(); var lat = Number(pos.lat.toFixed(5)); var lng = Number(pos.lng.toFixed(5)); var z = this.map.getZoom(); window.location.hash = `map=${z}/${lat}/${lng}`; this.setLocalStorage("last_zoom", this.map.getZoom()); this.setLocalStorage("last_lat", lat); this.setLocalStorage("last_lng", lng); }; OuestmonbusApp.prototype.imgPicto = function(ligne) { return `/images/picto_${ligne}.png`; }; /** * Récupère les liens vers les fiches PDF des lignes */ OuestmonbusApp.prototype.initPDFLinks = function() { this.allpdflinks = {}; var url_api = `https://data.explore.star.fr/api/records/1.0/search?apikey=${apikey}&dataset=mkt-information-documents-td&q=&rows=200&facet=nomtype&facet=datedebutvalidite&facet=datefinvalidite&facet=nomcourtligne&refine.nomtype=Fiche+horaire+de+ligne` log.debug("call API mkt-information-documents-td"); $.getJSON(url_api, function(data) { var url_prefix = "https://data.explore.star.fr/explore/dataset/mkt-information-documents-td/files/" _.each(data.records, function(e, i) { var pdflink_id = e.fields.file.id; var ligne_id = e.fields.idligne; this.allpdflinks[ligne_id] = url_prefix + pdflink_id + '/download/'; }.bind(this)); }.bind(this)).fail(function() { this.app.displayAPIError("Impossible de récupérer les url des PDF"); }.bind(this)); } OuestmonbusApp.prototype.showOuestmonbusNewsDlg = function() { this.hideDlg(true); var dialog = $("#ouestmonbus_news_dlg").data("dialog"); this.dialogCurrentDisplay = dialog; dialog.open(); this.updateDialogPosition("#ouestmonbus_news_dlg"); } OuestmonbusApp.prototype.hideDlg = function(dlg) { if (dlg) { if (this.dialogCurrentDisplay) { this.dialogCurrentDisplay.close(); this.dialogCurrentDisplay = null; } } }; /** * Initialise les boutons du menu. */ OuestmonbusApp.prototype.initMenuButtons = function() { // Bouton News ouestmonbus $("#ouestmonbus_btn").click(function() { this.showOuestmonbusNewsDlg(); }.bind(this)); if (this.md.mobile()) { $("#ouestmonbus_btn").unbind("mouseenter mouseleave"); } // Bouton localisation $("#localise_btn").click(function() { this.hideDlg(true); this.addWaitCenterSpinner(); this.localisation.autoLocate(); }.bind(this)); if (this.md.mobile()) { $("#localise_btn").unbind("mouseenter mouseleave"); } // photon search $("#address_btn").click(function() { this.hideDlg(true); $(".photon-input").show(); $(".photon-input").focus(); }.bind(this)); if (this.md.mobile()) { $("#address_btn").unbind("mouseenter mouseleave"); } // Affichage des perturbations $("#perturbations_btn").click(function() { var h = Math.ceil(($(window).height() - $(".app-bar").height() - $("#progressbar").height() - $("#legal_attribution").height()) * 0.8); $("#trafic_dlg_content").height(h); this.hideDlg(true); var dialog = $("#trafic_dlg").data("dialog"); this.dialogCurrentDisplay = dialog; dialog.open(); this.updateDialogPosition("#trafic_dlg"); }.bind(this)); if (this.md.mobile()) { $("#perturbations_btn").unbind("mouseenter mouseleave"); } // Bouton config $("#config_btn").click(function() { var val = this.getLocalStorage("all_stations_mode") === 'true'; $("#all_stations_checkbox").prop('checked', val); this.hideDlg(true); var dialog = $("#config_dlg").data("dialog"); this.dialogCurrentDisplay = dialog; dialog.open(); this.updateDialogPosition("#config_dlg"); }.bind(this)); if (this.md.mobile()) { $("#config_btn").unbind("mouseenter mouseleave"); } // Validation de la config $("#config_valid_btn").click(function() { var all_stations_mode = $("#all_stations_checkbox").is(":checked"); this.setLocalStorage("all_stations_mode", all_stations_mode); location.reload(); }.bind(this)); if (this.md.mobile()) { $("#config_valid_btn").unbind("mouseenter mouseleave"); } // Bouton aide $("#about_btn").click(function() { this.hideDlg(true); var dialog = $("#about_dlg").data("dialog"); this.dialogCurrentDisplay = dialog; dialog.open(); this.updateDialogPosition("#about_dlg"); }.bind(this)); if (this.md.mobile()) { $("#about_btn").unbind("mouseenter mouseleave"); } }; /** * Affichage de la notice d'aide. */ OuestmonbusApp.prototype.showHelp = function() { if (this.needHelp) { $.Notify({ content: "Cliquez ou tapez sur les icônes " + "station " + "des arrêts de bus pour connaître les prochains passages en temps réel.", icon: "", type: "info", keepOpen: true }); } this.setLocalStorage("alreadyDisplayHelp", true); }; /** * Affichage de la notification de légende retard. */ OuestmonbusApp.prototype.displayRetardHelp = function() { if (this.firstdisplayRetardHelp) { $.Notify({ content: "Le symbole " + "indique l'avance ou le retard par rapport à l'horaire initialement prévu", type: "info", keepOpen: true }); } this.firstdisplayRetardHelp = false; this.setLocalStorage("alreadyDisplayRetardHelp", true); }; /** * Affichage de la notification de légende pas de bus. */ OuestmonbusApp.prototype.displayNoBusHelp = function() { if (this.firstdisplayNoBusHelp) { $.Notify({ content: "Le symbole " + "indique qu'un bus est prévu avant la fin du service mais pas dans l'heure à venir", type: "warning", keepOpen: true }); } this.firstdisplayNoBusHelp = false; this.setLocalStorage("alreadyDisplayNoBusHelp", true); }; /** * Récupère le chemin des données des stations du jour. * @param {moment} now - maintenant * @return {string} - chemin */ OuestmonbusApp.prototype.getDataStationPath = function(now) { var first = moment(now); first.hour(5); first.minute(0); first.second(0); first.millisecond(0); if (now.isBefore(first)) { now = now.subtract(1, "days"); } var datestr = now.format("YYYY_MM_DD"); return `./data/stations_${datestr}.geojson`; }; /** * Récupère la liste des station. * * @param {function} onSucess - fonction à executer après la récupération */ OuestmonbusApp.prototype.getAllTodayStationsData = function(onSuccess) { var url_data = this.getDataStationPath(moment()); if (this.allStationsDataPath !== url_data || this.allStationsDataCache === null) { this.addWaitCenterSpinner(); $.getJSON(url_data, function(data) { this.allStationsDataCache = data; this.setLocalStorage("allStationsDataPath", url_data); this.setLocalStorage("allStationsDataCache", JSON.stringify(data)); onSuccess(data); }.bind(this)).fail(function() { this.removeWaitCenterSpinner(); this.displayAPIError("Connexion impossible aux données ouestmonbus"); }.bind(this)); } else { onSuccess(this.allStationsDataCache); } }; /** * Filtre si il faut afficher la station ou non * @param {moment} now * @param {array} limits - [premier_passage,dernier_passage] * @return {boolean} - true si à afficher */ OuestmonbusApp.prototype.stationTimeFilter = function(now, limits) { // prevent error in GTFS data if (limits === null || limits[0] === null || limits[1] === null) { return false; } var now_ajusted = moment(now); var morning_start = moment(now).startOf("day"); var morning_end = moment(morning_start).hours(5); var hour_end_limit = Number(limits[1].slice(0, 2)); if (now.isBetween(morning_start, morning_end) && hour_end_limit >= 24) { now_ajusted.add(1, "days"); } var start = moment(now); start.hours(Number(limits[0].slice(0, 2)) - 1); start.minute(Number(limits[0].slice(3, 5))); start.second(Number(limits[0].slice(6, 8))); var end = moment(now); end.hours(Number(limits[1].slice(0, 2))); end.minute(Number(limits[1].slice(3, 5))); end.second(Number(limits[1].slice(6, 8))); end.add(10, "minutes"); if (now_ajusted.isBetween(start, end)) { return true; } else { return false; } }; OuestmonbusApp.prototype.isMetroLine = function(lines) { var line_id = Number(lines[0].split(",")[0]); var res = _.size(lines) === 1 && (line_id === 1001 || line_id === 1002); return res; }; /*** * * Affiche les stations sur la ligne active * */ OuestmonbusApp.prototype.displayStationsOnActiveLine = function(line, sens) { this.deleteAllSelectedStationsOnMap(); var line_sens = `${line},${sens}`; var iconpath = "/images/station_selected.png"; if (this.isMetroLine([line_sens])) { iconpath = "/images/station-metro_selected.png"; } // récupère les stations sur la ligne var stations = _.filter(this.allStationsDataCache, function(e) { return _.contains(e.properties.lines, line + ',' + sens); }); // Génère un layer pour ces stations var geoJsonLayer = L.geoJson(stations, { pointToLayer: function(feature, latlng) { var stationIcon = L.icon({ iconUrl: iconpath, shadowUrl: "", iconSize: [22, 22], shadowSize: [0, 0], iconAnchor: [11, 11], shadowAnchor: [0, 0], popupAnchor: [0, 0] }); return L.marker(latlng, { icon: stationIcon }); }, onEachFeature: function(feature, marker) { marker.idarret = feature.properties.id; marker.nomarret = feature.properties.name; marker.lines = feature.properties.lines; marker.terminus = feature.properties.terminus; marker.parcours = feature.properties.parcours; marker.line_selected = true; this.onEachMakerAdd(marker); }.bind(this) }); this.stations_on_active_line_layer_group.addLayer(geoJsonLayer); // ajust icon size this.onMapZoomEnd(); } /** Callback lors d'un click sur une ligne */ OuestmonbusApp.prototype.displayLine = function(line, sens) { this.displayStationsOnActiveLine(line, sens); // met toutes les lignes en invisibles _.each(this.lines_layers, function(e) { var line_color = e.color; var invisible = { opacity: 0, color: line_color }; e.setStyle(invisible); }.bind(this)); // recupère la ligne selectionnée var line_layer = _.filter(this.lines_layers, function(e) { return (e.idligne === line && e.sens == sens); }); var param = [`${line},${sens}`]; this.displayLines(param, [], () => {}); }; /** * Affichage des lignes sur la carte * @param {strings} lines "idligne,sens" * @param {array[string]} extra_lines "ajoute une ligne qui n'est pas dans les parcours de la station (bus exceptionnel stade,..)" */ OuestmonbusApp.prototype.displayLines = function(lines, extra_lines, fdone) { log.debug(`displayLines: ${lines} | ${extra_lines}`); // Efface les précédentes lignes en cache this.deleteAllSelectedStationsOnMap(); _.each(this.lines_layers, function(layer) { this.map.removeLayer(layer); }.bind(this)); this.lines_layers = []; var parcours_array = _.map(this.markerCurrentDisplay.parcours, function(item) { return "id=" + item; }); var q = parcours_array.join(" OR "); if (extra_lines.length > 0) { var extra_lines_array = _.map(extra_lines, function(e) { return "idligne=" + e.split(",")[0]; }); q += " OR " + extra_lines_array.join(" OR "); } if (lines.length === 1) { var line = lines[0].split(",")[0]; q += " OR idligne=" + line; } // Ajoute les ligne dans l'ordre pour que la première ligne soit en premier plan. var first_line = lines[0].split(",")[0]; var lines_array = _.union(lines, extra_lines); var args = [first_line, lines_array, fdone]; var q = encodeURIComponent(q); if (this.isMetroLine(lines)) { var url_api = `https://data.explore.star.fr/api/records/1.0/search?apikey=${apikey}&dataset=tco-metro-topologie-parcours-td&sort=nomcourtligne&facet=nomcourtligne&facet=senscommercial&facet=type&facet=nomarretdepart&facet=nomarretarrivee&facet=estaccessiblepmr&q=${q}&timezone=${tz}`; log.debug("call API tco-metro-topologie-parcours-td"); } else { var url_api = `https://data.explore.star.fr/api/records/1.0/search?apikey=${apikey}&dataset=tco-bus-topologie-parcours-td&facet=idligne&facet=nomcourtligne&facet=senscommercial&facet=type&facet=nomarretdepart&facet=nomarretarrivee&facet=estaccessiblepmr&q=${q}&timezone=${tz}`; log.debug("call API tco-bus-topologie-parcours-td"); } $.getJSON(url_api, args, function(data) { var first_line = args[0]; var lines_array = args[1]; var fdone = args[2]; var data_sorted = _.sortBy(data.records, function(i) { return i.fields.idligne === first_line; }); _.each(data_sorted, function(trace, index, list) { // Si première ligne on selectionne if (index == (list.length - 1)) { log.debug(`First line is ${trace.fields.idligne}`); this.displayStationsOnActiveLine(trace.fields.idligne, trace.fields.sens); } // Bug de l'API: trace.fields.couleurtracetrace au lieu de trace.fields.couleurtrace quand métro. var line_color = trace.fields.couleurtrace !== undefined ? trace.fields.couleurtrace : trace.fields.couleurtracetrace; // filtre avec les parcours et montre la ligne uniquement si sur un des parcours de la station active // (garde les données pour la position des bus mais n'affiche pas sinon) var line_style_visible = { opacity: 1, color: line_color }; var line_sens = `${trace.fields.idligne},${trace.fields.sens}`; if (lines_array.includes(line_sens)) { log.debug(`Add ${line_sens} to map`); var layer = L.geoJson(trace.fields.parcours, { style: line_style_visible }); var sensstr = ""; if (Number(trace.fields.sens) === 0) { sensstr = "aller"; } else { sensstr = "retour"; } layer.bindPopup(this.generatePopupLineContent(trace.fields.idligne, sensstr, trace.fields.couleurtrace, trace.fields.libellelong, this.allpdflinks[trace.fields.idligne])); layer.on("click", function(e) { $(".line_info").parent().css("margin", 0); $(".leaflet-popup-tip-container").remove(); e.layer.bringToFront(); }.bind(this)); layer.idligne = trace.fields.idligne; layer.sens = trace.fields.sens; layer.dest = trace.fields.nomarretarrivee; layer.color = line_color; this.lines_layers.push(layer); layer.addTo(this.map); } }.bind(this)); fdone(); }.bind(this)); }; /** * Génération du contenu de la popup des lignes */ OuestmonbusApp.prototype.generatePopupLineContent = function(idligne, sens, couleur, nom, pdfurl) { var img_picto = this.imgPicto(idligne); var pdf_part = pdfurl ? `
Téléchargement de la fiche horaire` : ``; return `
${idligne} ${nom} (sens ${sens})
${pdf_part}
` }; /** * Affiche les stations. * Cette fonction devrait régulièrement être appelé pour mettre a jour * la présence ou non des arrêts * * @param {geoJson} geoJsonData - stations du jour */ OuestmonbusApp.prototype.displayAllStations = function(geoJsonData) { this.removeWaitCenterSpinner(); var geoJsonLayer = L.geoJson(geoJsonData, { pointToLayer: function(feature, latlng) { var busIcon = L.icon({ iconUrl: "./images/station.png", shadowUrl: "", iconSize: [22, 22], shadowSize: [0, 0], iconAnchor: [11, 11], shadowAnchor: [0, 0], popupAnchor: [0, 0] }); var metroIcon = L.icon({ iconUrl: "./images/station-metro.png", shadowUrl: "", iconSize: [22, 22], shadowSize: [0, 0], iconAnchor: [11, 11], shadowAnchor: [0, 0], popupAnchor: [0, 0] }); var icon = busIcon; var line_id = Number(feature.properties.lines[0].split(",")[0]); if (line_id === 1001 || line_id === 1002) { icon = metroIcon; } return L.marker(latlng, { icon: icon }); }.bind(this), filter: function(feature) { var all_stations_mode = this.getLocalStorage("all_stations_mode") === 'true'; var time_ok = this.stationTimeFilter(moment(), feature.properties.limits); return all_stations_mode || time_ok; }.bind(this), onEachFeature: function(feature, marker) { marker.idarret = feature.properties.id; marker.nomarret = feature.properties.name; marker.lines = feature.properties.lines; marker.terminus = feature.properties.terminus; marker.parcours = feature.properties.parcours; marker.line_selected = false; this.onEachMakerAdd(marker); if (this.firstStationDisplaySession) { if (this.stationFoundInCenter === null) { var lat = Number(marker.getLatLng().lat).toFixed(5); var lng = Number(marker.getLatLng().lng).toFixed(5); if (this.map.getCenter().distanceTo(L.latLng(lat, lng)) < this.map.DEFAULT_DISTANCE_RESOLUTION * 2) { this.stationFoundInCenter = marker; } } } }.bind(this) }); this.map.cluster_markers.clearLayers(); this.map.cluster_markers.addLayer(geoJsonLayer); if (!this.map.hasLayer(this.map.cluster_markers)) { this.map.cluster_markers.addTo(this.map); } if (this.stationFoundInCenter !== null) { this.openStation(this.stationFoundInCenter); } else { if (this.firstStationDisplaySession && this.map.getZoom() >= this.map.DEFAULT_ZOOM) { this.localisation.showLocation(this.map.getCenter(), false, false, 0); } } this.firstStationDisplaySession = false; this.delayRefreshAllStations(); }; /** * Appelé à chaque ajout de marker * * @param {marker} */ OuestmonbusApp.prototype.onEachMakerAdd = function(marker) { var popup = L.popup({ autoPan: true, autoPanPaddingTopLeft: L.point(Math.ceil($(window).width() * 0.05), $(".app-bar").height() + Math.ceil(($(window).height() - $(".app-bar").height()) * 0.05)), maxWidth: Math.ceil($(window).width() * 0.8), maxHeight: Math.ceil(($(window).height() - $(".app-bar").height() - $("#progressbar").height() - $("#legal_attribution").height()) * 0.8) }); popup.setContent("
"); marker.bindPopup(popup); marker.on("click", function() { this.openStation(marker); }.bind(this)); }; /** * Récupère les infos du prochain dépare à une station. * * @param {string} idarret * @param {function} onSuccess - callback */ OuestmonbusApp.prototype.getNextDepartures = function(lines, idarret, onSuccess, onFailed) { log.debug(`getNextDepartures: ${lines} ${idarret}`); if (this.isMetroLine(lines)) { var url = `https://data.explore.star.fr/api/records/1.0/search?apikey=${apikey}&dataset=tco-metro-circulation-passages-tr&sort=-depart&refine.idarret=${idarret}&timezone=${tz}`; log.debug("call API tco-metro-circulation-passages-tr"); } else { var url = `https://data.explore.star.fr/api/records/1.0/search?apikey=${apikey}&dataset=tco-bus-circulation-passages-tr&sort=-depart&refine.idarret=${idarret}&timezone=${tz}`; log.debug("call API tco-bus-circulation-passages-tr"); } $.getJSON(url, function(data) { if (data) { // check server time if (data.records.length > 0) { var record_timestamp = moment(data.records[0].record_timestamp); var diff_ms = moment().diff(record_timestamp); var d = moment.duration(diff_ms); // Feature ou bug de l'API, le champs .record_timestamp est à UTC ... var utc_offset = moment().utcOffset(); if (d.asMinutes() > (30 + utc_offset)) { var dialog = $("#api_time_failure_dlg").data("dialog"); this.dialogCurrentDisplay = dialog; dialog.open(); } onSuccess(data); } else { onSuccess(data); } } else { this.displayAPIError("données invalides du service opendata STAR"); onFailed(); } }.bind(this)).fail(function() { this.displayAPIError("échec de connexion au service opendata STAR"); onFailed(); }.bind(this)); }; OuestmonbusApp.prototype.deleteAllBusOnMap = function() { this.bus_group_layer.clearLayers(); }; OuestmonbusApp.prototype.deleteAllSelectedStationsOnMap = function() { this.stations_on_active_line_layer_group.clearLayers(); }; OuestmonbusApp.prototype.generatePopupBusContent = function(idligne, destination) { var img_picto = this.imgPicto(idligne); return `
${idligne}
Direction ${destination}
`; }; OuestmonbusApp.prototype.getBusPositions = function(data, latlng) { // prepare data var all_records = _.reduce(data.records, function(seed, item) { seed.push(item.fields); return seed; }, []); var lignes_selected = _.groupBy(all_records, "idligne"); lignes_selected = _.map(lignes_selected, function(item) { var first = _.first(item); return [`${first.idligne},${first.sens}`]; }); lignes_selected = _.flatten(lignes_selected); var request = _.map(lignes_selected, function(e) { var idligne = e.split(",")[0]; var sens = e.split(",")[1]; return `idligne=${idligne} AND sens=${sens}`; }); var q = encodeURIComponent(request.join(" OR ")); var url_api = `https://data.explore.star.fr/api/records/1.0/search?apikey=${apikey}&dataset=tco-bus-vehicules-position-tr&q=${q}&timezone=${tz}&refine.etat=En+ligne`; log.debug("call API tco-bus-vehicules-position-tr"); $.getJSON(url_api, function(data) { if (data) { // Ajoute la ligne comme clé de dict var records = _.groupBy(data.records, function(e) { return `${e.fields.idligne},${e.fields.sens}`; }); // Position de la station var station_l_latlng = this.normalizeLatLng(latlng); // Résultat des positions classé par distance de la station et uniquement les ** 4 premiers bus ** var records_distance_filtred = _.mapObject(records, function(r) { return _.first(_.sortBy(r, function(e) { return this.normalizeLatLng(e.geometry.coordinates).distanceTo(station_l_latlng); }.bind(this)), 4); }.bind(this)); // Ligne avec idlgine comme clé var lines_grouped = _.groupBy(this.lines_layers, function(e) { return `${e.idligne},${e.sens}`; }); var lines = _.mapObject(lines_grouped, function(e) { return _.map(e, function(x) { return x.getLayers()[0]._latlngs; }); }); _.each(records_distance_filtred, function(value, key) { this.genSegmentsTrajet(key, value, lines[key]); }.bind(this)); } }.bind(this) ).fail(function() { // TODO add error message }.bind(this)); return; } /* Normalise les coordonnées. L'api peut répondre un array dans les deux sens. */ OuestmonbusApp.prototype.normalizeLatLng = function(a) { if (a[0] > 47 && a[1] < 0) { return L.latLng(a[0], a[1]); } if (a[1] > 47 && a[0] < 0) { return L.latLng(a[1], a[0]); } if (a.lat > 47 && a.lng < 0) { return L.latLng(a.lat, a.lng); } if (a.lat < 0 && a.lng > 47) { return L.latLng(a.lng, a.lat); } // Ne devrait jamais se produire log.debug(`Error: bad conversion: ${a}`); } OuestmonbusApp.prototype.genSegmentsTrajet = function(ligne_sens, bus_positions, lines) { _.each(bus_positions, function(bus_position) { var idbus = bus_position.fields.idbus; var url_api = `https://data.explore.star.fr/api/records/1.0/search?apikey=${apikey}&sort=-depart&dataset=tco-bus-circulation-passages-tr&refine.idbus=${idbus}&timezone=${tz}`; var idligne = ligne_sens.split(',')[0]; var sens = ligne_sens.split(',')[1]; log.debug("call API tco-bus-circulation-passages-tr"); $.getJSON(url_api, function(data) { if (data) { // On ne tient pas compte des stations à moins de 10 secondes var records_10s_filtred = _.filter(data.records, function(e) { var now = moment(); var station_depart = moment(e.fields.depart); var diff_second = station_depart.diff(now, 'seconds'); return diff_second >= 10; }); // On utilise les 3 prochaines stations pour calculer le déplacement var first_next_stations = _.first(records_10s_filtred, 3); if (first_next_stations.length == 0) { log.debug(`Pas de stations trouvé pour le bus ${idbus} - ${ligne_sens}`); } else { // Selection la ligne pour la trajectoire var selected_line = this.getLignesForNextStation(first_next_stations, lines); if (selected_line) { var segments_time = this.getParcoursSegmentsTimes(bus_position, first_next_stations, selected_line); if (!_.isUndefined(segments_time) && segments_time[0].length > 0) { this.addBusIconOnMap(idbus, segments_time[0], segments_time[1], idligne, sens, bus_position.fields.destination); return; } else { log.debug(`Impossible de calculer la trajectoire pour le bus ${idbus} - ${ligne_sens}`); } } else { log.debug(`Pas de ligne trouvé pour la trajectoire du bus ${idbus} - ${ligne_sens}`); } } log.debug("Ajout d'un bus sans animation") var uniq_position = [bus_position.fields.coordonnees, bus_position.fields.coordonnees]; this.addBusIconOnMap(idbus, uniq_position, 0, idligne, sens, bus_position.fields.destination); } }.bind(this)); }.bind(this)); }; /* Cette fonction prend en paramètre, la position du bus, les 3 prochaine stations et la ligne sur lequel le bus circule. Elle retourne un tableau [0]: un array de L.latLng du déplacement du bus à venir, [1]: le temps estimé que va prendre le bus pour parcourir le trajet. Le trajet est calculé en utilisant les prochaines station jusqu'à 2 minutes de trajet soit atteint. */ OuestmonbusApp.prototype.getParcoursSegmentsTimes = function(bus_position, first_next_stations, line) { var parcour_points = []; var parcour_duration = 0; // calcul position du point de départ var first_point = L.GeometryUtil.closest(this.map, line, this.normalizeLatLng(bus_position.fields.coordonnees), true); log.debug(`Distance depuis le point de départ: ${first_point.distance}`); if (first_point.distance > 250) { log.debug("Le point de départ est hors parcours"); return; } // index dans la ligne du point de départ du bus var first_index = _.findIndex(line, function(e) { return e.lat == first_point.lat && e.lon == first_point.lon; }); var begin_index = first_index; _.each(first_next_stations, function(e) { if (parcour_duration < STATION_CONTENT_REFRESH) { var next_station_point = L.GeometryUtil.closest(this.map, line, this.normalizeLatLng(e.fields.coordonnees), true); if (next_station_point.distance > 250) { log.debug("Bus hors parcours"); return; } var next_station_index = _.findIndex(line, function(x) { return x.lat == next_station_point.lat && x.lon == next_station_point.lon; }); var part_line = line.slice(begin_index, next_station_index); begin_index = next_station_index; var part_duration = moment(e.fields.depart).diff(moment()); parcour_points = _.union(parcour_points, part_line); parcour_duration += part_duration; } }.bind(this)); return [parcour_points, parcour_duration]; }; /* Prend en paramètre les 3 prochaines stations d'un bus et les parcours connus du bus. Retourne le parcours le plus proche des 3 prochaines stations. Si > 2 km on retourn null, on considère que le parcours est inconnu. */ OuestmonbusApp.prototype.getLignesForNextStation = function(first_next_stations, lines) { // Genere un array des positions des prochaines stations var first_next_stations_array = _.map(first_next_stations, function(e) { return e.geometry.coordinates; }); // pour chaque ligne var distances = _.map(lines, function(line_pos) { var distance = 0; // Pour chaque station _.each(first_next_stations_array, function(station_pos) { // On inverse lat et lon car API Star donne dans le sens inverse Leaflet var pos_station = this.normalizeLatLng(station_pos); // Le point le plus proche de la station sur tous les points de la ligne var c = L.GeometryUtil.closest(this.map, line_pos, pos_station); // On fait la somme des distances distance += c.distance; }.bind(this)); return [line_pos, distance]; }.bind(this)); var res = _.first(_.sortBy(distances, function(e) { return e[1] })); if (_.isUndefined(res)) { log.debug("Pas de ligne trouvé"); return; } if (res[1] > 2000) { log.debug("Le bus est situé à plus de 2 km de son parcours"); return; } return res[0]; } OuestmonbusApp.prototype.addBusIconOnMap = function(idbus, parcours, durations, idligne, sens, destination) { log.debug(`Ajout de bus sur la carte idligne: ${idligne}`); var busIcon = L.icon({ iconUrl: this.imgPicto(idligne), shadowUrl: "./images/bus_shadow.png", iconSize: [20, 20], shadowSize: [32, 32], iconAnchor: [10, 10], shadowAnchor: [16, 16], popupAnchor: [0, 0] }); var marker = L.Marker.movingMarker(parcours, durations, { autostart: true, icon: busIcon, zIndexOffset: -2 }); marker.idligne = idligne; marker.sens = sens; marker.idbus = idbus; marker.last_refresh = moment(); marker.bindPopup(this.generatePopupBusContent(idligne, destination), { closeButton: false, className: "bus_popup" }); marker.on("click", function() { $(".bus_info").parent().css("margin", 0); $(".leaflet-popup-tip-container").remove(); }.bind(this)); this.updateBusMarker(marker); this.refreshBusCountHtml(); }; /* Enleve l'ancien layer du bus et ajoute le nouveau. */ OuestmonbusApp.prototype.updateBusMarker = function(busMarker) { var bus_already_on_map = false; this.bus_group_layer.eachLayer(function(layer) { if (layer.idbus === busMarker.idbus) { this.bus_group_layer.removeLayer(layer); this.bus_group_layer.addLayer(busMarker); bus_already_on_map = true; } }.bind(this)); if (!bus_already_on_map) { this.bus_group_layer.addLayer(busMarker); } }; OuestmonbusApp.prototype.refreshBusCountHtml = function() { var bus_count_on_map = 0; this.bus_group_layer.eachLayer(function(layer) { if (!_.isUndefined(layer.idbus)) { bus_count_on_map++; } }); var content = ""; if (bus_count_on_map === 0) { content = "Pas de bus sur la carte pour cet arrêt"; } if (bus_count_on_map > 1) { content = `Voir les ${bus_count_on_map} bus sur la carte`; } if (bus_count_on_map === 1) { content = "Voir le bus sur la carte"; } $("#bus_count").html(content); $("#bus_count").click(this.onBusCountClick.bind(this)); }; /* Callback lors du click sur le bouton ce recentrage de la carte sur les bus. */ OuestmonbusApp.prototype.onBusCountClick = function() { var bus_map_bounds = [this.markerCurrentDisplay.getLatLng()]; this.bus_group_layer.eachLayer(function(layer) { if (!_.isUndefined(layer.idbus)) { bus_map_bounds.push(layer.getLatLng()); } }.bind(this)); this.map.fitBounds(bus_map_bounds, { padding: L.point(100, 100) }); }; /** * Ouvre la popup d'une station * * @param {L.marker} marker - */ OuestmonbusApp.prototype.openStation = function(marker) { this.needHelp = false; this.delayRefreshAllStations(); this.markerCurrentDisplay = marker; this.getNextDepartures(marker.lines, marker.idarret, function(data) { this.setPopupContent(marker, data); marker.openPopup(); if (Number(data.nhits) > 0) { this.refreshLinesBus(marker, data); } }.bind(this), function() { this.setPopupContent(marker, null); marker.openPopup(); }.bind(this)); this.refreshStation(); }; OuestmonbusApp.prototype.setOrigin = function(marker) { this.localisation.deleteMarkerMe.call(this.localisation); if (this.bus_station_origin !== null && this.map.hasLayer(this.bus_station_origin)) { this.map.removeLayer(this.bus_station_origin); } this.bus_station_origin = L.marker(marker.getLatLng(), { zIndexOffset: -1 }); this.bus_station_origin.on("click", function(e) { this.moveMapTo(e.latlng, this.map.DEFAULT_ZOOM); }.bind(this)); this.bus_station_origin.addTo(this.map); }; /** * Fonction apppellé lorsque l'utilisateur clique sur la station la plus * proche après une localisation. */ OuestmonbusApp.prototype.followNearestStation = function() { this.moveMapTo(this.localisation.nearestStation.marker.getLatLng(), this.map.DEFAULT_ZOOM, this.openStation.bind(this, this.localisation.nearestStation.marker) ); }; /** * Mets à jour le contenue d'une popup de station */ OuestmonbusApp.prototype.setPopupContent = function(marker, data) { var content = this.generatePopupContent(marker, data); var popup = marker.getPopup(); popup.setContent(content); //popup.update(); }; OuestmonbusApp.prototype.getStationUrl = function(marker) { var z = this.map.DEFAULT_ZOOM; var lat = Number(marker.getLatLng().lat).toFixed(5); var lng = Number(marker.getLatLng().lng).toFixed(5); var station_url = `#map=${z}/${lat}/${lng}`; return station_url; }; OuestmonbusApp.prototype.generateImgHtml = function(idligne, sens) { var img_picto = this.imgPicto(idligne); return `${idligne}`; }; /** * Généré le contenu d'une popup de station. * * @param {L.marker} marker - marker de la station * @param {object} data - donnée reçu de l'API explore * * @return {string} - code html */ OuestmonbusApp.prototype.generatePopupContent = function(marker, data) { var station_url = this.getStationUrl(marker); var lines_header = []; var lines_img = ""; var lines_array = marker.lines; _.each(lines_array, function(item) { var a = item.split(","); if (_.indexOf(lines_header, a[0]) === -1) { lines_img += this.generateImgHtml(a[0], a[1]); lines_img += ' '; lines_header.push(a[0]); } }.bind(this)); var full_content = ""; // si il y a moins de 4 icones on les mets à coté du nom de la station if (_.size(lines_header) < 4) { full_content = `
`; } else { // sinon les icones sont dessous full_content = `
`; } if (data !== null) { var metrobus = this.isMetroLine(marker.lines) ? "métro" : "bus" if (Number(data.nhits) > 0) { // prepare data var all_records = _.reduce(data.records, function(seed, item) { seed.push(item.fields); return seed; }, []); this.refreshBusCountHtml(); var records = _.groupBy(all_records, "idligne"); var pluriel = all_records.length > 1 ? "s" : ""; full_content += `
prochain${pluriel} passage${pluriel} de ${metrobus}:
► Direction ${destination}`; } if (is_terminus) { destination = "  Terminus à cet arrêt"; } content += this.generateImgHtml(element.idligne, element.sens); content += `${destination}
`; } var accurate = (element.precision === "Temps réel") && (index === 0); content += this.generateLineDeparture( new Date(), element.depart, accurate, element.departtheorique, is_terminus, isMetro); }.bind(this)); return `
${content}
`; }; /** * Génère le code html de la ligne d'horaire (ex "12:56:00 dans 45min ! retard 5min") * * @param {string} server_localtime - heure courante du serveur d'API * @param {string} time - heure de passage * @param {Boolean} accurate - temps réel * @param {string} expected - heure théorique * * @return {string} code html */ OuestmonbusApp.prototype.generateLineDeparture = function(localtime, time, accurate, expected, is_terminus, isMetro) { var now = moment(); var departure = moment(time); var wait_min = departure.diff(now, "minutes"); expected = moment(expected); var retard_min = departure.diff(expected, "minutes"); var retard_min_prefix = ""; if (retard_min > 0) { retard_min_prefix = "en retard de"; } if (retard_min < 0) { retard_min_prefix = "en avance de"; } var retard_str = ""; var t = Math.abs(retard_min); if (accurate && (retard_min < -1)) { this.displayRetardHelp(); retard_str = ` ${retard_min_prefix} ${t} min`; } else if (accurate && (retard_min !== 0)) { this.displayRetardHelp(); retard_str = ` ${retard_min_prefix} ${t} min`; } var line_h = ""; var wait_str = ""; if (wait_min < 1) { if (is_terminus) { wait_str = " dans < 1 min"; } else { wait_str = " dans < 1 min"; } } else { if (wait_min < 3 && !is_terminus && !isMetro) { wait_str = `dans ${wait_min} min`; } else { if (wait_min > 60) { var hours = departure.diff(now, "hours"); var minutes = wait_min - (hours * 60); wait_str = `dans ${hours}h ${minutes} min`; } else { wait_str = `dans ${wait_min} min`; } } } var t = departure.format("HH:mm:ss"); line_h = `${t} ${wait_str} ${retard_str}`; return ``; }; /** * Informe l'utilisateur que l'API STAR est en rade.. */ OuestmonbusApp.prototype.displayAPIError = function(err) { if (this.markerCurrentDisplay) { this.markerCurrentDisplay.getPopup().setContent(`Erreur: ${err}`); } $.Notify({ caption: "Erreur", content: `Impossible d'interroger les données en temps réel (${err})`, type: "error" }); }; /** * Annule et relance un timer pour le rafraîchissement des stations. */ OuestmonbusApp.prototype.delayRefreshAllStations = function() { if (this.refreshAllStationsTimer) { clearTimeout(this.refreshAllStationsTimer); } this.refreshAllStationsTimer = setTimeout(function() { this.getAllTodayStationsData(this.displayAllStations.bind(this)); }.bind(this), STATION_LIST_REFRESH); }; OuestmonbusApp.prototype.refreshLinesBus = function(markerCurrentDisplay, data) { // prepare data var all_records = _.reduce(data.records, function(seed, item) { seed.push(item.fields); return seed; }, []); // Affichage des lignes var lines_sens = _.map(all_records, function(item) { return item.idligne + "," + item.sens; }); lines_sens = _.uniq(lines_sens); // extra lines : lignes annoncées au passage mais qui n'étaient pas prévu par les données GTFS // cas observé une fois, à vérifier si pertinent de garder le code. var extra_lines = _.reject(lines_sens, function(e) { return _.contains(this, e); }, this.markerCurrentDisplay.lines); var lignes_selected = _.groupBy(all_records, "idligne"); lignes_selected = _.map(lignes_selected, function(item) { var first = _.first(item); return ["" + first.idligne + "," + first.sens]; }); lignes_selected = _.flatten(lignes_selected); if ((lignes_selected.join() !== this.last_lignes_selected.join()) && markerCurrentDisplay.line_selected == false) { this.displayLines(lines_sens, extra_lines, function() { this.deleteAllBusOnMap(); this.getBusPositions(data, this.markerCurrentDisplay._latlng); this.last_lignes_selected = lignes_selected; this.last_ligne_bus_fetch = moment(); }.bind(this)); } else { var next_fresh_duration = moment().diff(this.last_ligne_bus_fetch); if (next_fresh_duration > STATION_CONTENT_REFRESH) { this.getBusPositions(data, this.markerCurrentDisplay._latlng); this.last_ligne_bus_fetch = moment(); } else { var t = STATION_CONTENT_REFRESH - next_fresh_duration; log.debug(`Prochain rafraichissement de la position des bus dans ${t} ms`); this.refreshBusCountHtml(); } } }; /** * Rafraichie le contenue de la station actuellement ouverte */ OuestmonbusApp.prototype.refreshStation = function() { this.startprogressBar(); if (this.refreshStationTimer) { clearTimeout(this.refreshStationTimer); } this.refreshStationTimer = setTimeout(function() { if (this.markerCurrentDisplay) { this.getNextDepartures(this.markerCurrentDisplay.lines, this.markerCurrentDisplay.idarret, function(data) { // Generation du contenue de la popup this.setPopupContent(this.markerCurrentDisplay, data); if (Number(data.nhits) > 0) { // Update lines and bus this.refreshLinesBus(this.markerCurrentDisplay, data); } }.bind(this), function() { this.setPopupContent(this.markerCurrentDisplay, null); }.bind(this)); this.refreshStation(); } }.bind(this), STATION_CONTENT_REFRESH); }; OuestmonbusApp.prototype.startprogressBar = function() { this.stopprogressBar(); this.progressbar.animate(1); }; OuestmonbusApp.prototype.stopprogressBar = function() { this.progressbar.stop(); this.progressbar.set(0); }; /////////////////////////////////////////////////////////////////////////////// ///////////////////////// Classe InfosTrafics ///////////////////////////////// /////////////////////////////////////////////////////////////////////////////// function InfosTrafics(app) { this.app = app; this.html = ""; this.count = 0; } InfosTrafics.prototype.fetchLinesAndAlerts = function() { var isoNow = moment().toISOString(false); var q = encodeURIComponent(`debutvalidite < ${isoNow} AND finvalidite > ${isoNow}`); var url_api = `https://data.explore.star.fr/api/records/1.0/search?apikey=${apikey}&dataset=tco-busmetro-trafic-alertes-tr&sort=idligne&rows=100&facet=idligne&q=${q}&timezone=${tz}`; this.html += "
"; log.debug("call API tco-busmetro-trafic-alertes-tr"); $.getJSON(url_api, function(data) { _.each(data.records, function(info) { var picto_html = this.getPictoHtml(info.fields.idligne); var c = this.formatDetail(info.fields.description); this.html += `
${info.fields.titre}
${picto_html}
${c}
`; this.count += 1; }.bind(this)); }.bind(this)).fail(function() { this.app.displayAPIError("Impossible de récupérer les infos trafics (connect)"); }.bind(this)).done(function() { this.html += "
"; $("#trafic_dlg_content").html(this.html); $("#perturbations_btn").attr("data-hint", `Infos trafic| ${this.count} pertubations`); }.bind(this)); }; InfosTrafics.prototype.getPictoHtml = function(ligne) { var img_picto = this.app.imgPicto(ligne); var html = `${ligne}`; return html; }; InfosTrafics.prototype.formatDetail = function(input) { return input.replace(/(?:\r\n|\r|\n)/g, "
"); }; /////////////////////////////////////////////////////////////////////////////// ///////////////////////// Classe Localization ///////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /** * Localisation de l'utilisateur. * @constructor */ function Localization(app, map) { this.app = app; this.map = map; this.marker_me = null; // position "vous êtes ici" this.circle_me = null; // rayon || this.nearestStation = null; this.last_lat = null; this.last_lng = null; this.last_localisation = null; this.current_loc = null; } /** * Localise l'utilisateur avec le navigateur. */ Localization.prototype.autoLocate = function() { this.map.locate({ setView: true, maxZoom: this.map.DEFAULT_ZOOM }); this.map.once("locationfound", this.onLocationFound.bind(this)); this.map.once("locationerror", this.onLocationError.bind(this)); }; /** * Appelé quand la localisation est trouvée. */ Localization.prototype.onLocationFound = function(e) { this.app.removeWaitCenterSpinner(); this.showLocation(e.latlng, true, true, Math.round(e.accuracy / 2)); }; Localization.prototype.deleteMarkerMe = function() { if (this.marker_me && this.map.hasLayer(this.marker_me)) { this.map.removeLayer(this.marker_me); } if (this.circle_me && this.map.hasLayer(this.circle_me)) { this.map.removeLayer(this.circle_me); } }; Localization.prototype.showLocation = function(latlng, youarehere, show_circle, radius) { if (this.map.getBounds().contains(latlng)) { if (radius > 1000) { this.onLocationError(); } else { this.deleteMarkerMe(); this.nearestStation = this.findNearestStation(latlng); if (this.nearestStation.marker) { if (this.nearestStation.distance < 50) { this.app.moveMapTo(this.nearestStation.marker.getLatLng(), this.map.DEFAULT_ZOOM, this.app.openStation.bind(app, this.nearestStation.marker)); } else { this.marker_me = L.marker(latlng); var station_url = this.app.getStationUrl(this.nearestStation.marker); var station_html = `
${this.nearestStation.marker.nomarret}
`; var marker_str = ""; if (youarehere) { marker_str = "Vous êtes ici !
"; if (this.nearestStation.distance < 1000) { var d = Math.round(this.nearestStation.distance); marker_str += `L'arrêt de bus le plus proche est:
${station_html} à ${d} m.`; } this.marker_me.bindPopup(marker_str); this.marker_me.addTo(this.map); this.marker_me.openPopup(); } else { if (this.nearestStation.distance < 1000) { var d = Math.round(this.nearestStation.distance); marker_str += `L'arrêt de bus le plus proche est:
${station_html} à ${d} m.`; this.marker_me.bindPopup(marker_str); this.marker_me.addTo(this.map); this.marker_me.openPopup(); } else { this.marker_me.addTo(this.map); } } } } if (show_circle) { this.circle_me = L.circle(latlng, radius).addTo(this.map); } } } else { var dialog = $("#localisation_nothere_dlg").data("dialog"); this.app.dialogCurrentDisplay = dialog; $("#localisation_nothere_dlg_close").click({ dlg: dialog }, function(e) { e.data.dlg.close(); $(".photon-input").show(); $(".photon-input").focus(); }); dialog.open(); this.app.updateDialogPosition("#localisation_nothere_dlg"); this.app.moveMapTo(this.map.DEFAULT_POSITION, this.map.DEFAULT_ZOOM); } }; Localization.prototype.findNearestStation = function(latlng) { var nearestStation = {}; var res = L.GeometryUtil.closestLayer(this.map, this.map.cluster_markers.getLayers(), latlng); if (res) { nearestStation.marker = res.layer; nearestStation.distance = res.distance; } return nearestStation; }; /** * Appelé quand la localisation n'est trouvée. */ Localization.prototype.onLocationError = function() { this.app.removeWaitCenterSpinner(); var dialog = $("#localisation_failed_dlg").data("dialog"); this.app.dialogCurrentDisplay = dialog; $("#localisation_failed_dlg_close").click({ dlg: dialog }, function(e) { e.data.dlg.close(); $(".photon-input").show(); $(".photon-input").focus(); }); dialog.open(); this.app.updateDialogPosition("#localisation_failed_dlg_close"); }; /** * Converti le hash URL en LatLng. * * @param {string} url */ Localization.prototype.urlToZoomLatLng = function(url) { var res = null; try { var map_pos = url.substring(url.indexOf("map")); var zoom_str_x = map_pos.indexOf("=") + 1; var lat_str_x = map_pos.indexOf("/") + 1; var lng_str_x = map_pos.indexOf("/", lat_str_x); var zoom = Number(map_pos.substring(zoom_str_x, lat_str_x - 1)); var lat = Number(map_pos.substring(lat_str_x, lng_str_x)).toFixed(5); var lng = Number(map_pos.substring(lng_str_x + 1)).toFixed(5); if (isNaN(lat) || isNaN(lng)) { throw "Nan"; } var latlng = L.latLng(lat, lng); if (!this.map.MAXBOUNDS.contains(latlng)) { throw "not in Map"; } res = [zoom, latlng]; } catch (err) { //log.debug("Err:"+err); res = null; } return res; };