/**
* @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/05/2023:**
- Hébergement: Changement de serveur. Utilisation du serveur de tuile OpenStreetMap France.
**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://{s}.tile.openstreetmap.fr/osmfr/{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 " +
" " +
"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 `
${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 `
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 sur la carte pour cet arrêt";
}
if (bus_count_on_map > 1) {
content = `Voir les ${bus_count_on_map} sur la carte`;
}
if (bus_count_on_map === 1) {
content = "Voir le 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 ``;
};
/**
* 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 = `