Files
ouestmonbus/ouestmonbus.js
2023-05-03 22:06:25 +02:00

2088 lines
70 KiB
JavaScript

/**
* @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 <http://www.gnu.org/licenses/>.
*/
"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 = "<div data-role='preloader' data-type='ring' data-style='dark'></div>";
};
// 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: "<div id='legal_attribution'>&copy; 2015-2023 <a href='https://ouestmonbus.com'>ouestmonbus.com</a>"
}).addAttribution("<a href='http://osm.org/copyright'>OpenStreetMap</a> | <a href='https://data.explore.star.fr/page/home/'>Data Keolis Rennes Métropole</a></div>");
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 " +
"<img src='./images/station.png' height='22' width='22' alt='station'> " +
"des arrêts de bus pour connaître les prochains passages en temps réel.",
icon: "<span class='mif-question'></span>",
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 <span class='mif-sync-problem mif-lg'></span> " +
"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 <span class='mif-not'></span> " +
"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 ? `<br><a target='_blank' href='${pdfurl}'>Téléchargement de la fiche horaire</a>` : ``;
return `<div class='line_info' style='border:0.3rem solid #${couleur};'><img src='${img_picto}' height='22' width='22' alt='${idligne}'> ${nom} (sens ${sens})<br>${pdf_part}</div>`
};
/**
* 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("<div data-role='preloader' data-type='ring' data-style='dark'></div>");
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 `<div class='bus_info'><img src='${img_picto}' height='22' width='22' alt='${idligne}'><br><span>Direction ${destination}</span></div>`;
};
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 <img src='./images/bus_shadow.png' height='22' width='22' alt='bus'> sur la carte pour cet arrêt";
}
if (bus_count_on_map > 1) {
content = `Voir les <b>${bus_count_on_map}</b> <img src='./images/bus_shadow.png' height='22' width='22' alt='bus'> sur la carte`;
}
if (bus_count_on_map === 1) {
content = "Voir le <img src='./images/bus_shadow.png' height='22' width='22' alt='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 `<a href='#' alt='afficher ligne' onclick='app.displayLine("${idligne}","${sens}");return false;'><img src='${img_picto}' height='22' width='22' alt='${idligne}'></a>`;
};
/**
* 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 = `<div class='popup_title_inline'><div class='list_lines_inline'>${lines_img}</div><div class='station_name'><a class='fg-white' href='${station_url}'>${marker.nomarret}</a></div></div><!-- idarret=${marker.idarret} --> <hr>`;
} else { // sinon les icones sont dessous
full_content = `<div class='popup_title'><div class='station_name'><a class='fg-white' href='${station_url}'>${marker.nomarret}</a></div><div class='list_lines'>${lines_img}</div></div><!-- idarret=${marker.idarret} --> <hr>`;
}
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 += `<div class='header_station'><b>prochain${pluriel} passage${pluriel} de ${metrobus}:</b></div><div class='lines_content'`;
_.each(records, function(record_line) {
_.each(_.groupBy(record_line, "destination"), function(item) {
var lines_sens = _.first(item).idligne + "," + _.first(item).sens;
var is_terminus = (_.indexOf(marker.terminus, lines_sens) !== -1);
var content = this.generateLineContent(this.isMetroLine(marker.lines), is_terminus, item);
full_content += content;
}.bind(this));
}.bind(this));
} else {
this.displayNoBusHelp();
this.stopprogressBar();
full_content += "<div class='line_content'><span class='mif-not'></span> pas de passage dans l'heure</div>";
}
full_content += "</div><br>";
if (!this.isMetroLine(marker.lines)) {
full_content += "<button style='width:100%; text-align:center;' id='bus_count'>Pas de <img src='./images/bus_shadow.png' height='22' width='22' alt='bus'> sur la carte pour cet arrêt</button>";
}
} else {
full_content += "<div class='header_station' style='height: 1rem; width:9rem; display: inline-block;'></div>";
}
full_content += "</div>";
return full_content;
};
/**
* Génère le contenu de la ligne dans la popup de station.
*
* @param {object} records - champs de records des données de l'API explore
* @param {string} idligne - numéro de ligne
*
* @return {string} code html
*/
OuestmonbusApp.prototype.generateLineContent = function(isMetro, is_terminus, record) {
var content = "";
if (isMetro && is_terminus) {
return "";
}
_.each(_.first(record, 2), function(element, index) {
if (index === 0) {
var destination = element.destination;
if (_.isUndefined(destination)) {
destination = "Ligne " + element.idligne;
} else {
destination = destination.replace("- | ", "-");
destination = destination.replace(" | ", "&nbsp;");
destination = `<b>&#9658;&nbsp;Direction&nbsp;${destination}</b>`;
}
if (is_terminus) {
destination = "<b><font color='#787878 '>&nbsp; Terminus à cet arrêt</font></b>";
}
content += this.generateImgHtml(element.idligne, element.sens);
content += `<span>${destination}</span><br>`;
}
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 `<div class='line_content'>${content}</div>`;
};
/**
* 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 = ` <span class='mif-sync-problem mif-lg fg-red'></span> <b>${retard_min_prefix} ${t} min</b>`;
} else
if (accurate && (retard_min !== 0)) {
this.displayRetardHelp();
retard_str = ` <span class='mif-sync-problem mif-lg'></span> <b>${retard_min_prefix} ${t} min</b>`;
}
var line_h = "";
var wait_str = "";
if (wait_min < 1) {
if (is_terminus) {
wait_str = "<span><b> dans < 1 min</b></span>";
} else {
wait_str = "<span class= 'fg-red'><b> dans < 1 min</b></span>";
}
} else {
if (wait_min < 3 && !is_terminus && !isMetro) {
wait_str = `<span class= 'fg-red'>dans <b>${wait_min} min</b></span>`;
} else {
if (wait_min > 60) {
var hours = departure.diff(now, "hours");
var minutes = wait_min - (hours * 60);
wait_str = `<span>dans <b>${hours}h ${minutes} min</b></span>`;
} else {
wait_str = `<span>dans <b>${wait_min} min</b></span>`;
}
}
}
var t = departure.format("HH:mm:ss");
line_h = `<u>${t}</u> ${wait_str} ${retard_str}`;
return `<div class='popup_line'>${line_h}</div>`;
};
/**
* Informe l'utilisateur que l'API STAR est en rade..
*/
OuestmonbusApp.prototype.displayAPIError = function(err) {
if (this.markerCurrentDisplay) {
this.markerCurrentDisplay.getPopup().setContent(`<b>Erreur:</b> ${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 += "<div class='accordion' data-role='accordion'>";
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 += `<div class='frame'><div class='heading'>${info.fields.titre}<div class='place-right'>${picto_html}</div></div><div class='content'>${c}</div></div>`;
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 += "</div>";
$("#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 = `<img src='${img_picto}' height='22' width='22' alt='${ligne}' style='margin-right:2px;'>`;
return html;
};
InfosTrafics.prototype.formatDetail = function(input) {
return input.replace(/(?:\r\n|\r|\n)/g, "<br>");
};
///////////////////////////////////////////////////////////////////////////////
///////////////////////// 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 = `<a class='fg-white' id='nearestStationLink' href='${station_url}' onclick='app.followNearestStation();return false;'><div class='station_name'>${this.nearestStation.marker.nomarret}</div></a>`;
var marker_str = "";
if (youarehere) {
marker_str = "<b>Vous êtes ici !</b><br>";
if (this.nearestStation.distance < 1000) {
var d = Math.round(this.nearestStation.distance);
marker_str += `L'arrêt de bus le plus proche est: <br>${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: <br>${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;
};