up speed limit map
This commit is contained in:
parent
8c1380ec04
commit
32b803115e
2 changed files with 567 additions and 385 deletions
542
assets/app.js
542
assets/app.js
|
@ -8,13 +8,10 @@
|
||||||
// any CSS you import will output into a single css file (app.css in this case)
|
// any CSS you import will output into a single css file (app.css in this case)
|
||||||
import './styles/app.css';
|
import './styles/app.css';
|
||||||
import jQuery from 'jquery';
|
import jQuery from 'jquery';
|
||||||
window.$ = jQuery;
|
|
||||||
window.jQuery = jQuery;
|
|
||||||
import 'tablesort/tablesort.css';
|
import 'tablesort/tablesort.css';
|
||||||
|
|
||||||
// start the Stimulus application
|
// start the Stimulus application
|
||||||
// import './bootstrap';
|
// import './bootstrap';
|
||||||
|
|
||||||
import './utils.js';
|
import './utils.js';
|
||||||
import './opening_hours.js';
|
import './opening_hours.js';
|
||||||
import './josm.js';
|
import './josm.js';
|
||||||
|
@ -27,17 +24,21 @@ import Chart from 'chart.js/auto';
|
||||||
import ChartDataLabels from 'chartjs-plugin-datalabels';
|
import ChartDataLabels from 'chartjs-plugin-datalabels';
|
||||||
import maplibregl from 'maplibre-gl';
|
import maplibregl from 'maplibre-gl';
|
||||||
import {
|
import {
|
||||||
genererCouleurPastel,
|
adjustListGroupFontSize,
|
||||||
setupCitySearch,
|
enableLabourageForm,
|
||||||
handleAddCityFormSubmit,
|
genererCouleurPastel,
|
||||||
enableLabourageForm,
|
getLabourerUrl,
|
||||||
getLabourerUrl,
|
handleAddCityFormSubmit,
|
||||||
adjustListGroupFontSize,
|
setupCitySearch,
|
||||||
toggleCompletionInfo,
|
toggleCompletionInfo,
|
||||||
updateMapHeightForLargeScreens
|
updateMapHeightForLargeScreens
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import tableSortJs from 'table-sort-js/table-sort.js';
|
import tableSortJs from 'table-sort-js/table-sort.js';
|
||||||
import 'chartjs-adapter-date-fns';
|
import 'chartjs-adapter-date-fns';
|
||||||
|
|
||||||
|
window.$ = jQuery;
|
||||||
|
window.jQuery = jQuery;
|
||||||
|
|
||||||
console.log('TableSort', tableSortJs)
|
console.log('TableSort', tableSortJs)
|
||||||
|
|
||||||
// Charger table-sortable (version non minifiée locale)
|
// Charger table-sortable (version non minifiée locale)
|
||||||
|
@ -58,303 +59,302 @@ Chart.register(ChartDataLabels);
|
||||||
|
|
||||||
// Attendre le chargement du DOM
|
// Attendre le chargement du DOM
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
console.log('DOMContentLoaded');
|
console.log('DOMContentLoaded');
|
||||||
|
|
||||||
if(updateMapHeightForLargeScreens){
|
if (updateMapHeightForLargeScreens) {
|
||||||
|
|
||||||
|
|
||||||
window.addEventListener('resize', updateMapHeightForLargeScreens);
|
window.addEventListener('resize', updateMapHeightForLargeScreens);
|
||||||
}
|
}
|
||||||
|
|
||||||
const randombg = genererCouleurPastel();
|
const randombg = genererCouleurPastel();
|
||||||
// Appliquer la couleur au body
|
// Appliquer la couleur au body
|
||||||
|
|
||||||
document.querySelectorAll('body, .edit-land, .body-landing').forEach(element => {
|
document.querySelectorAll('body, .edit-land, .body-landing').forEach(element => {
|
||||||
element.style.backgroundColor = randombg;
|
element.style.backgroundColor = randombg;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Gestion du bouton pour afficher tous les champs
|
// Gestion du bouton pour afficher tous les champs
|
||||||
const btnShowAllFields = document.querySelector('#showAllFields');
|
const btnShowAllFields = document.querySelector('#showAllFields');
|
||||||
if (btnShowAllFields) {
|
if (btnShowAllFields) {
|
||||||
console.log('btnShowAllFields détecté');
|
console.log('btnShowAllFields détecté');
|
||||||
btnShowAllFields.addEventListener('click', () => {
|
btnShowAllFields.addEventListener('click', () => {
|
||||||
// Sélectionner tous les inputs dans le formulaire
|
// Sélectionner tous les inputs dans le formulaire
|
||||||
const form = document.querySelector('form');
|
const form = document.querySelector('form');
|
||||||
if (form) {
|
if (form) {
|
||||||
// Sélectionner tous les inputs sauf #validation_messages
|
// Sélectionner tous les inputs sauf #validation_messages
|
||||||
const hiddenInputs = form.querySelectorAll('#advanced_tags');
|
const hiddenInputs = form.querySelectorAll('#advanced_tags');
|
||||||
|
|
||||||
hiddenInputs.forEach(input => {
|
hiddenInputs.forEach(input => {
|
||||||
input.classList.toggle('d-none');
|
input.classList.toggle('d-none');
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const btnClosedCommerce = document.querySelector('#closedCommerce');
|
||||||
|
if (btnClosedCommerce) {
|
||||||
|
btnClosedCommerce.addEventListener('click', () => {
|
||||||
|
|
||||||
|
if (!confirm('Êtes-vous sûr de vouloir signaler ce commerce comme fermé ?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.location.href = '/closed_commerce/' + document.getElementById('app_public_closed_commerce').value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openingHoursFormManager.init();
|
||||||
|
|
||||||
|
|
||||||
|
// Vérifier si l'élément avec l'ID 'userChangesHistory' existe avant d'appeler la fonction
|
||||||
|
if (document.getElementById('userChangesHistory')) {
|
||||||
|
listChangesets();
|
||||||
|
} else {
|
||||||
|
console.log('userChangesHistory non trouvé');
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('input[type="text"]').forEach(input => {
|
||||||
|
input.addEventListener('blur', updateCompletionProgress);
|
||||||
});
|
});
|
||||||
}
|
const form = document.querySelector('form')
|
||||||
const btnClosedCommerce = document.querySelector('#closedCommerce');
|
if (form) {
|
||||||
if (btnClosedCommerce) {
|
form.addEventListener('submit', check_validity);
|
||||||
btnClosedCommerce.addEventListener('click', () => {
|
updateCompletionProgress()
|
||||||
|
}
|
||||||
|
updateCompletionProgress();
|
||||||
|
|
||||||
if (!confirm('Êtes-vous sûr de vouloir signaler ce commerce comme fermé ?')) {
|
// Focus sur le premier champ texte au chargement
|
||||||
return;
|
// const firstTextInput = document.querySelector('input.form-control');
|
||||||
}
|
// if (firstTextInput) {
|
||||||
window.location.href = '/closed_commerce/' + document.getElementById('app_public_closed_commerce').value;
|
// firstTextInput.focus();
|
||||||
});
|
// console.log('focus sur le premier champ texte', firstTextInput);
|
||||||
}
|
// } else {
|
||||||
|
// console.log('pas de champ texte trouvé');
|
||||||
openingHoursFormManager.init();
|
// }
|
||||||
|
|
||||||
|
|
||||||
// Vérifier si l'élément avec l'ID 'userChangesHistory' existe avant d'appeler la fonction
|
parseCuisine();
|
||||||
if (document.getElementById('userChangesHistory')) {
|
|
||||||
listChangesets();
|
|
||||||
} else {
|
|
||||||
console.log('userChangesHistory non trouvé');
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelectorAll('input[type="text"]').forEach(input => {
|
// Modifier la fonction de recherche existante
|
||||||
input.addEventListener('blur', updateCompletionProgress);
|
const searchInput = document.getElementById('app_admin_labourer');
|
||||||
});
|
const suggestionList = document.getElementById('suggestionList');
|
||||||
const form = document.querySelector('form')
|
|
||||||
if (form) {
|
|
||||||
form.addEventListener('submit', check_validity);
|
|
||||||
updateCompletionProgress()
|
|
||||||
}
|
|
||||||
updateCompletionProgress();
|
|
||||||
|
|
||||||
// Focus sur le premier champ texte au chargement
|
if (searchInput && suggestionList) {
|
||||||
// const firstTextInput = document.querySelector('input.form-control');
|
let timeoutId;
|
||||||
// if (firstTextInput) {
|
|
||||||
// firstTextInput.focus();
|
|
||||||
// console.log('focus sur le premier champ texte', firstTextInput);
|
|
||||||
// } else {
|
|
||||||
// console.log('pas de champ texte trouvé');
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
searchInput.addEventListener('input', () => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
const query = searchInput.value.trim();
|
||||||
|
|
||||||
|
if (query.length < 2) {
|
||||||
|
suggestionList.innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutId = setTimeout(async () => {
|
||||||
|
const suggestions = await searchInseeCode(query);
|
||||||
|
suggestionList.innerHTML = '';
|
||||||
|
|
||||||
parseCuisine();
|
if (suggestions.length === 0) {
|
||||||
|
const li = document.createElement('li');
|
||||||
// Modifier la fonction de recherche existante
|
li.style.cssText = `
|
||||||
const searchInput = document.getElementById('app_admin_labourer');
|
|
||||||
const suggestionList = document.getElementById('suggestionList');
|
|
||||||
|
|
||||||
if (searchInput && suggestionList) {
|
|
||||||
let timeoutId;
|
|
||||||
|
|
||||||
searchInput.addEventListener('input', () => {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
const query = searchInput.value.trim();
|
|
||||||
|
|
||||||
if (query.length < 2) {
|
|
||||||
suggestionList.innerHTML = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
timeoutId = setTimeout(async () => {
|
|
||||||
const suggestions = await searchInseeCode(query);
|
|
||||||
suggestionList.innerHTML = '';
|
|
||||||
|
|
||||||
if (suggestions.length === 0) {
|
|
||||||
const li = document.createElement('li');
|
|
||||||
li.style.cssText = `
|
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
color: #666;
|
color: #666;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
`;
|
`;
|
||||||
li.textContent = 'Aucun résultat trouvé';
|
li.textContent = 'Aucun résultat trouvé';
|
||||||
suggestionList.appendChild(li);
|
suggestionList.appendChild(li);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
suggestions.forEach(suggestion => {
|
suggestions.forEach(suggestion => {
|
||||||
const li = document.createElement('li');
|
const li = document.createElement('li');
|
||||||
li.style.cssText = `
|
li.style.cssText = `
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid #eee;
|
||||||
`;
|
`;
|
||||||
li.textContent = suggestion.label;
|
li.textContent = suggestion.label;
|
||||||
|
|
||||||
li.addEventListener('mouseenter', () => {
|
li.addEventListener('mouseenter', () => {
|
||||||
li.style.backgroundColor = '#f0f0f0';
|
li.style.backgroundColor = '#f0f0f0';
|
||||||
});
|
});
|
||||||
|
|
||||||
li.addEventListener('mouseleave', () => {
|
li.addEventListener('mouseleave', () => {
|
||||||
li.style.backgroundColor = 'white';
|
li.style.backgroundColor = 'white';
|
||||||
});
|
});
|
||||||
|
|
||||||
li.addEventListener('click', () => {
|
li.addEventListener('click', () => {
|
||||||
searchInput.value = suggestion.insee;
|
searchInput.value = suggestion.insee;
|
||||||
suggestionList.innerHTML = '';
|
suggestionList.innerHTML = '';
|
||||||
labourer();
|
labourer();
|
||||||
});
|
});
|
||||||
|
|
||||||
suggestionList.appendChild(li);
|
suggestionList.appendChild(li);
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
});
|
});
|
||||||
}, 300);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if(enableLabourageForm){
|
|
||||||
|
|
||||||
enableLabourageForm();
|
|
||||||
}
|
|
||||||
adjustListGroupFontSize('.list-group-item');
|
|
||||||
|
|
||||||
// Activer le tri naturel sur tous les tableaux avec la classe table-sort
|
|
||||||
if (tableSortJs) {
|
|
||||||
tableSortJs();
|
|
||||||
}else{
|
|
||||||
console.log('pas de tablesort')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialisation du tri et filtrage sur les tableaux du dashboard et de la page stats
|
|
||||||
// if (document.querySelector('#dashboard-table')) {
|
|
||||||
// $('#dashboard-table').tableSortable({
|
|
||||||
// pagination: false,
|
|
||||||
// showPaginationLabel: true,
|
|
||||||
// searchField: '#dashboard-table-search',
|
|
||||||
// responsive: false
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// if (document.querySelector('#stats-table')) {
|
|
||||||
// $('#stats-table').tableSortable({
|
|
||||||
// pagination: false,
|
|
||||||
// showPaginationLabel: true,
|
|
||||||
// searchField: '#stats-table-search',
|
|
||||||
// responsive: false
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Correction pour le formulaire de labourage
|
|
||||||
const labourerForm = document.getElementById('labourerForm');
|
|
||||||
if (labourerForm) {
|
|
||||||
labourerForm.addEventListener('submit', async function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const zipInput = document.getElementById('selectedZipCode');
|
|
||||||
const cityInput = document.getElementById('citySearch');
|
|
||||||
let insee = zipInput.value;
|
|
||||||
if (!insee && cityInput && cityInput.value.trim().length > 0) {
|
|
||||||
// Recherche du code INSEE via l'API
|
|
||||||
const response = await fetch(`https://geo.api.gouv.fr/communes?nom=${encodeURIComponent(cityInput.value.trim())}&fields=nom,code&limit=1`);
|
|
||||||
const data = await response.json();
|
|
||||||
if (data.length > 0) {
|
|
||||||
insee = data[0].code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (insee) {
|
|
||||||
window.location.href = `/add-city-without-labourage/${insee}`;
|
|
||||||
} else {
|
|
||||||
alert('Veuillez sélectionner une ville valide.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ajouter un écouteur pour l'événement 'load' de MapLibre afin d'ajuster la hauteur de la carte
|
|
||||||
if (window.maplibregl && document.getElementById('map')) {
|
|
||||||
// On suppose que la carte est initialisée ailleurs et accessible via window.mapInstance
|
|
||||||
// Sinon, on peut essayer de détecter l'instance automatiquement
|
|
||||||
let mapInstance = window.mapInstance;
|
|
||||||
if (!mapInstance && window.maplibreMap) {
|
|
||||||
mapInstance = window.maplibreMap;
|
|
||||||
}
|
}
|
||||||
// Si l'instance n'est pas trouvée, essayer de la récupérer via une variable globale courante
|
|
||||||
if (!mapInstance && window.map) {
|
if (enableLabourageForm) {
|
||||||
mapInstance = window.map;
|
|
||||||
|
enableLabourageForm();
|
||||||
}
|
}
|
||||||
if (mapInstance && typeof mapInstance.on === 'function') {
|
adjustListGroupFontSize('.list-group-item');
|
||||||
mapInstance.on('load', function() {
|
|
||||||
updateMapHeightForLargeScreens();
|
// Activer le tri naturel sur tous les tableaux avec la classe table-sort
|
||||||
});
|
if (tableSortJs) {
|
||||||
|
tableSortJs();
|
||||||
|
} else {
|
||||||
|
console.log('pas de tablesort')
|
||||||
}
|
}
|
||||||
}
|
|
||||||
//updateMapHeightForLargeScreens();
|
|
||||||
|
|
||||||
console.log('window.followupSeries',window.followupSeries)
|
// Correction pour le formulaire de labourage
|
||||||
if (!window.followupSeries) return;
|
const labourerForm = document.getElementById('labourerForm');
|
||||||
|
if (labourerForm) {
|
||||||
|
labourerForm.addEventListener('submit', async function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const zipInput = document.getElementById('selectedZipCode');
|
||||||
|
const cityInput = document.getElementById('citySearch');
|
||||||
|
let insee = zipInput.value;
|
||||||
|
if (!insee && cityInput && cityInput.value.trim().length > 0) {
|
||||||
|
// Recherche du code INSEE via l'API
|
||||||
|
const response = await fetch(`https://geo.api.gouv.fr/communes?nom=${encodeURIComponent(cityInput.value.trim())}&fields=nom,code&limit=1`);
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.length > 0) {
|
||||||
|
insee = data[0].code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (insee) {
|
||||||
|
window.location.href = `/add-city-without-labourage/${insee}`;
|
||||||
|
} else {
|
||||||
|
alert('Veuillez sélectionner une ville valide.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const series = window.followupSeries;
|
// Ajouter un écouteur pour l'événement 'load' de MapLibre afin d'ajuster la hauteur de la carte
|
||||||
|
if (window.maplibregl && document.getElementById('map')) {
|
||||||
// Données bornes de recharge
|
// On suppose que la carte est initialisée ailleurs et accessible via window.mapInstance
|
||||||
const chargingStationCount = (series['charging_station_count'] || []).map(point => ({ x: point.date, y: point.value }));
|
// Sinon, on peut essayer de détecter l'instance automatiquement
|
||||||
const chargingStationCompletion = (series['charging_station_completion'] || []).map(point => ({ x: point.date, y: point.value }));
|
let mapInstance = window.mapInstance;
|
||||||
|
if (!mapInstance && window.maplibreMap) {
|
||||||
// Données bornes incendie
|
mapInstance = window.maplibreMap;
|
||||||
const fireHydrantCount = (series['fire_hydrant_count'] || []).map(point => ({ x: point.date, y: point.value }));
|
|
||||||
const fireHydrantCompletion = (series['fire_hydrant_completion'] || []).map(point => ({ x: point.date, y: point.value }));
|
|
||||||
|
|
||||||
// Graphique bornes de recharge
|
|
||||||
const chargingStationChart = document.getElementById('chargingStationChart');
|
|
||||||
if (chargingStationChart) {
|
|
||||||
new Chart(chargingStationChart, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'Nombre de bornes de recharge',
|
|
||||||
data: chargingStationCount,
|
|
||||||
borderColor: 'blue',
|
|
||||||
backgroundColor: 'rgba(0,0,255,0.1)',
|
|
||||||
fill: false,
|
|
||||||
yAxisID: 'y',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Complétion (%)',
|
|
||||||
data: chargingStationCompletion,
|
|
||||||
borderColor: 'green',
|
|
||||||
backgroundColor: 'rgba(0,255,0,0.1)',
|
|
||||||
fill: false,
|
|
||||||
yAxisID: 'y1',
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
parsing: false,
|
|
||||||
responsive: true,
|
|
||||||
scales: {
|
|
||||||
x: { type: 'time', time: { unit: 'day' }, title: { display: true, text: 'Date' } },
|
|
||||||
y: { beginAtZero: true, title: { display: true, text: 'Nombre' } },
|
|
||||||
y1: { beginAtZero: true, position: 'right', title: { display: true, text: 'Complétion (%)' }, grid: { drawOnChartArea: false } }
|
|
||||||
}
|
}
|
||||||
}
|
// Si l'instance n'est pas trouvée, essayer de la récupérer via une variable globale courante
|
||||||
});
|
if (!mapInstance && window.map) {
|
||||||
}
|
mapInstance = window.map;
|
||||||
|
|
||||||
// Graphique bornes incendie
|
|
||||||
const fireHydrantChart = document.getElementById('fireHydrantChart');
|
|
||||||
if (fireHydrantChart) {
|
|
||||||
new Chart(fireHydrantChart, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'Nombre de bornes incendie',
|
|
||||||
data: fireHydrantCount,
|
|
||||||
borderColor: 'red',
|
|
||||||
backgroundColor: 'rgba(255,0,0,0.1)',
|
|
||||||
fill: false,
|
|
||||||
yAxisID: 'y',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Complétion (%)',
|
|
||||||
data: fireHydrantCompletion,
|
|
||||||
borderColor: 'orange',
|
|
||||||
backgroundColor: 'rgba(255,165,0,0.1)',
|
|
||||||
fill: false,
|
|
||||||
yAxisID: 'y1',
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
parsing: false,
|
|
||||||
responsive: true,
|
|
||||||
scales: {
|
|
||||||
x: { type: 'time', time: { unit: 'day' }, title: { display: true, text: 'Date' } },
|
|
||||||
y: { beginAtZero: true, title: { display: true, text: 'Nombre' } },
|
|
||||||
y1: { beginAtZero: true, position: 'right', title: { display: true, text: 'Complétion (%)' }, grid: { drawOnChartArea: false } }
|
|
||||||
}
|
}
|
||||||
}
|
if (mapInstance && typeof mapInstance.on === 'function') {
|
||||||
});
|
mapInstance.on('load', function () {
|
||||||
}
|
updateMapHeightForLargeScreens();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//updateMapHeightForLargeScreens();
|
||||||
|
|
||||||
|
console.log('window.followupSeries', window.followupSeries)
|
||||||
|
if (!window.followupSeries) return;
|
||||||
|
|
||||||
|
const series = window.followupSeries;
|
||||||
|
|
||||||
|
// Données bornes de recharge
|
||||||
|
const chargingStationCount = (series['charging_station_count'] || []).map(point => ({
|
||||||
|
x: point.date,
|
||||||
|
y: point.value
|
||||||
|
}));
|
||||||
|
const chargingStationCompletion = (series['charging_station_completion'] || []).map(point => ({
|
||||||
|
x: point.date,
|
||||||
|
y: point.value
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Données bornes incendie
|
||||||
|
const fireHydrantCount = (series['fire_hydrant_count'] || []).map(point => ({x: point.date, y: point.value}));
|
||||||
|
const fireHydrantCompletion = (series['fire_hydrant_completion'] || []).map(point => ({
|
||||||
|
x: point.date,
|
||||||
|
y: point.value
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Graphique bornes de recharge
|
||||||
|
const chargingStationChart = document.getElementById('chargingStationChart');
|
||||||
|
if (chargingStationChart) {
|
||||||
|
new Chart(chargingStationChart, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Nombre de bornes de recharge',
|
||||||
|
data: chargingStationCount,
|
||||||
|
borderColor: 'blue',
|
||||||
|
backgroundColor: 'rgba(0,0,255,0.1)',
|
||||||
|
fill: false,
|
||||||
|
yAxisID: 'y',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Complétion (%)',
|
||||||
|
data: chargingStationCompletion,
|
||||||
|
borderColor: 'green',
|
||||||
|
backgroundColor: 'rgba(0,255,0,0.1)',
|
||||||
|
fill: false,
|
||||||
|
yAxisID: 'y1',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
parsing: false,
|
||||||
|
responsive: true,
|
||||||
|
scales: {
|
||||||
|
x: {type: 'time', time: {unit: 'day'}, title: {display: true, text: 'Date'}},
|
||||||
|
y: {beginAtZero: true, title: {display: true, text: 'Nombre'}},
|
||||||
|
y1: {
|
||||||
|
beginAtZero: true,
|
||||||
|
position: 'right',
|
||||||
|
title: {display: true, text: 'Complétion (%)'},
|
||||||
|
grid: {drawOnChartArea: false}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Graphique bornes incendie
|
||||||
|
const fireHydrantChart = document.getElementById('fireHydrantChart');
|
||||||
|
if (fireHydrantChart) {
|
||||||
|
new Chart(fireHydrantChart, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Nombre de bornes incendie',
|
||||||
|
data: fireHydrantCount,
|
||||||
|
borderColor: 'red',
|
||||||
|
backgroundColor: 'rgba(255,0,0,0.1)',
|
||||||
|
fill: false,
|
||||||
|
yAxisID: 'y',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Complétion (%)',
|
||||||
|
data: fireHydrantCompletion,
|
||||||
|
borderColor: 'orange',
|
||||||
|
backgroundColor: 'rgba(255,165,0,0.1)',
|
||||||
|
fill: false,
|
||||||
|
yAxisID: 'y1',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
parsing: false,
|
||||||
|
responsive: true,
|
||||||
|
scales: {
|
||||||
|
x: {type: 'time', time: {unit: 'day'}, title: {display: true, text: 'Date'}},
|
||||||
|
y: {beginAtZero: true, title: {display: true, text: 'Nombre'}},
|
||||||
|
y1: {
|
||||||
|
beginAtZero: true,
|
||||||
|
position: 'right',
|
||||||
|
title: {display: true, text: 'Complétion (%)'},
|
||||||
|
grid: {drawOnChartArea: false}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,134 +3,316 @@
|
||||||
{% block title %}Limites de vitesse - {{ stats.name }}{% endblock %}
|
{% block title %}Limites de vitesse - {{ stats.name }}{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container mt-4">
|
<div class="container-fluid">
|
||||||
<h1><i class="bi bi-speedometer2"></i> Limites de vitesse à {{ stats.name }} ({{ stats.zone }})</h1>
|
<div class="row">
|
||||||
<p>Complétion des limitations de vitesse sur le réseau routier OSM.<br>
|
<!-- Sidebar de navigation -->
|
||||||
<span class="text-muted">Tags attendus :
|
<div class="col-12 col-lg-3">
|
||||||
|
{{ stats.name }} :
|
||||||
|
{% include 'admin/_city_sidebar.html.twig' with {'stats': stats, 'active_menu': 'speed-limit'} %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contenu principal -->
|
||||||
|
<div class="col-lg-9 col--12 main-content">
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<h1><i class="bi bi-speedometer2"></i> Limites de vitesse à {{ stats.name }} ({{ stats.zone }})
|
||||||
|
</h1>
|
||||||
|
<p>Complétion des limitations de vitesse sur le réseau routier OSM.<br>
|
||||||
|
<span class="text-muted">Tags attendus :
|
||||||
{% for tag in expected_tags %}<code>{{ tag }}</code>{% if not loop.last %}, {% endif %}{% endfor %}
|
{% for tag in expected_tags %}<code>{{ tag }}</code>{% if not loop.last %}, {% endif %}{% endfor %}
|
||||||
</span></p>
|
</span></p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<div class="card-header"><i class="bi bi-map"></i> Carte des routes (coloration selon maxspeed)</div>
|
<div class="card-header"><i class="bi bi-map"></i> Carte des routes (coloration
|
||||||
<div class="card-body p-2">
|
selon maxspeed)
|
||||||
<div id="speedlimit-map" style="height: 450px; width: 100%;"></div>
|
</div>
|
||||||
<div id="speedlimit-completion" class="mt-2"></div>
|
<div class="card-body p-2">
|
||||||
</div>
|
<div id="speedlimit-map" style="height: 450px; width: 100%;"></div>
|
||||||
</div>
|
<div id="speedlimit-completion" class="mt-2"></div>
|
||||||
<div class="card mb-3">
|
</div>
|
||||||
<div class="card-header"><i class="bi bi-traffic-cone"></i> Panneaux routiers & feux de circulation</div>
|
</div>
|
||||||
<div class="card-body p-2">
|
<div class="card mb-3">
|
||||||
<div id="traffic-map" style="height: 350px; width: 100%;"></div>
|
<div class="card-header"><i class="bi bi-traffic-cone"></i> Panneaux routiers & feux
|
||||||
</div>
|
de circulation
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="card-body p-2">
|
||||||
<div class="col-md-4">
|
<div class="row">
|
||||||
<div class="card">
|
<div class="col-md-8">
|
||||||
<div class="card-header"><i class="bi bi-info-circle"></i> Informations</div>
|
<div id="traffic-map" style="height: 350px; width: 100%;"></div>
|
||||||
<div class="card-body">
|
</div>
|
||||||
<ul>
|
<div class="col-md-4">
|
||||||
<li><b>Rouge</b> : route sans <code>maxspeed</code></li>
|
<div id="traffic-stats" class="mt-2">
|
||||||
<li><b>Vert</b> : route avec <code>maxspeed</code></li>
|
<h5>Objets trouvés</h5>
|
||||||
<li><b>Bleu</b> : panneau routier (<code>traffic_sign</code>)</li>
|
<div id="traffic-objects-list">Chargement...</div>
|
||||||
<li><b>Orange</b> : feu de circulation (<code>traffic_signals</code>)</li>
|
</div>
|
||||||
</ul>
|
</div>
|
||||||
<p>Cliquer sur un objet pour ouvrir dans OSM, iD ou JOSM.</p>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><i class="bi bi-info-circle"></i> Informations</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<ul>
|
||||||
|
<li><b>Rouge</b> : route sans <code>maxspeed</code></li>
|
||||||
|
<li><b>Vert</b> : route avec <code>maxspeed</code></li>
|
||||||
|
<li><b>Bleu</b> : panneau routier (<code>traffic_sign</code>)</li>
|
||||||
|
<li><b>Orange</b> : feu de circulation (<code>traffic_signals</code>)</li>
|
||||||
|
</ul>
|
||||||
|
<p>Cliquer sur un objet pour ouvrir dans OSM, iD ou JOSM.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block javascripts %}
|
{% block javascripts %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
<script src="/js/maplibre/maplibre-gl.js"></script>
|
<script src="/js/maplibre/maplibre-gl.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const insee = '{{ insee_code }}';
|
const insee = '{{ insee_code }}';
|
||||||
const overpassHighways = `[out:json][timeout:60];\narea["ref:INSEE"="${insee}"]->.searchArea;\nway["highway"](area.searchArea);\nout body;\n>;\nout skel qt;`;
|
const overpassHighways = `[out:json][timeout:60];\narea["ref:INSEE"="${insee}"]->.searchArea;\nway["highway"~"^(primary|secondary|tertiary|primary_link|secondary_link)$"](area.searchArea);\nout body;\n>;\nout skel qt;`;
|
||||||
const overpassTraffic = `[out:json][timeout:60];\narea["ref:INSEE"="${insee}"]->.searchArea;\n(
|
const overpassTraffic = `[out:json][timeout:60];\narea["ref:INSEE"="${insee}"]->.searchArea;\n(
|
||||||
node["traffic_sign"](area.searchArea);
|
node["traffic_sign"](area.searchArea);
|
||||||
node["highway"="traffic_signals"](area.searchArea);
|
node["highway"="traffic_signals"](area.searchArea);
|
||||||
);\nout body;`;
|
);\nout body;`;
|
||||||
|
|
||||||
function fetchOverpass(query) {
|
function fetchOverpass(query) {
|
||||||
return fetch('https://overpass-api.de/api/interpreter', {
|
return fetch('https://overpass-api.de/api/interpreter', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: query,
|
body: query,
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
|
||||||
}).then(r => r.json());
|
}).then(r => r.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
// --- Carte des highways ---
|
// --- Carte des highways ---
|
||||||
let map = new maplibregl.Map({
|
let map = new maplibregl.Map({
|
||||||
container: 'speedlimit-map',
|
container: 'speedlimit-map',
|
||||||
style: 'https://api.maptiler.com/maps/streets/style.json?key={{ maptiler_token }}',
|
style: 'https://api.maptiler.com/maps/streets/style.json?key={{ maptiler_token }}',
|
||||||
center: [2, 48],
|
center: [2, 48],
|
||||||
zoom: 13
|
zoom: 13
|
||||||
});
|
});
|
||||||
map.addControl(new maplibregl.NavigationControl());
|
map.addControl(new maplibregl.NavigationControl());
|
||||||
fetchOverpass(overpassHighways).then(data => {
|
fetchOverpass(overpassHighways).then(data => {
|
||||||
let nodes = {};
|
let nodes = {};
|
||||||
let withMaxspeed = 0, total = 0;
|
let withMaxspeed = 0, total = 0;
|
||||||
data.elements.forEach(e => { if (e.type === 'node') nodes[e.id] = e; });
|
let allCoords = [];
|
||||||
data.elements.forEach(e => {
|
data.elements.forEach(e => {
|
||||||
if (e.type === 'way' && e.tags && e.tags.highway) {
|
if (e.type === 'node') nodes[e.id] = e;
|
||||||
total++;
|
});
|
||||||
let hasMaxspeed = !!e.tags.maxspeed;
|
data.elements.forEach(e => {
|
||||||
if (hasMaxspeed) withMaxspeed++;
|
if (e.type === 'way' && e.tags && e.tags.highway) {
|
||||||
let coords = e.nodes.map(nid => nodes[nid]).filter(n => n).map(n => [n.lon, n.lat]);
|
total++;
|
||||||
if (coords.length < 2) return;
|
let hasMaxspeed = !!e.tags.maxspeed;
|
||||||
map.addLayer({
|
if (hasMaxspeed) withMaxspeed++;
|
||||||
id: 'way-' + e.id,
|
let coords = e.nodes.map(nid => nodes[nid]).filter(n => n).map(n => [n.lon, n.lat]);
|
||||||
type: 'line',
|
if (coords.length < 2) return;
|
||||||
source: {
|
// Store all coordinates for calculating bounds
|
||||||
type: 'geojson',
|
allCoords = allCoords.concat(coords);
|
||||||
data: { type: 'Feature', geometry: { type: 'LineString', coordinates: coords }, properties: {} }
|
map.addLayer({
|
||||||
},
|
id: 'way-' + e.id,
|
||||||
layout: { 'line-join': 'round', 'line-cap': 'round' },
|
type: 'line',
|
||||||
paint: {
|
source: {
|
||||||
'line-color': hasMaxspeed ? '#28a745' : '#dc3545',
|
type: 'geojson',
|
||||||
'line-width': 3
|
data: {
|
||||||
|
type: 'Feature',
|
||||||
|
geometry: {type: 'LineString', coordinates: coords},
|
||||||
|
properties: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layout: {'line-join': 'round', 'line-cap': 'round'},
|
||||||
|
paint: {
|
||||||
|
'line-color': hasMaxspeed ? '#28a745' : '#dc3545',
|
||||||
|
'line-width': 3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Popup OSM/iD/JOSM
|
||||||
|
let popupHtml = `<b>Highway ${e.tags.highway}</b><br>ID: ${e.id}`;
|
||||||
|
if (e.tags.maxspeed) popupHtml += `<br><b>maxspeed:</b> ${e.tags.maxspeed}`;
|
||||||
|
popupHtml += `<br><a href='https://www.openstreetmap.org/way/${e.id}' target='_blank'>OSM</a> | <a href='https://www.openstreetmap.org/edit?editor=id&way=${e.id}' target='_blank'>iD</a> | <a href='http://127.0.0.1:8111/load_object?objects=W${e.id}' target='_blank'>JOSM</a>`;
|
||||||
|
let marker = new maplibregl.Marker({color: hasMaxspeed ? '#28a745' : '#dc3545'})
|
||||||
|
.setLngLat(coords[Math.floor(coords.length / 2)])
|
||||||
|
.setPopup(new maplibregl.Popup({offset: 18}).setHTML(popupHtml));
|
||||||
|
// marker.addTo(map); // Optionnel : afficher un marker au milieu
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Popup OSM/iD/JOSM
|
let completion = total > 0 ? Math.round(100 * withMaxspeed / total) : 0;
|
||||||
let popupHtml = `<b>Highway ${e.tags.highway}</b><br>ID: ${e.id}`;
|
let withoutMaxspeed = total - withMaxspeed;
|
||||||
if (e.tags.maxspeed) popupHtml += `<br><b>maxspeed:</b> ${e.tags.maxspeed}`;
|
|
||||||
popupHtml += `<br><a href='https://www.openstreetmap.org/way/${e.id}' target='_blank'>OSM</a> | <a href='https://www.openstreetmap.org/edit?editor=id&way=${e.id}' target='_blank'>iD</a> | <a href='http://127.0.0.1:8111/load_object?objects=W${e.id}' target='_blank'>JOSM</a>`;
|
// Center map on roads if we have coordinates
|
||||||
let marker = new maplibregl.Marker({ color: hasMaxspeed ? '#28a745' : '#dc3545' })
|
if (allCoords.length > 0) {
|
||||||
.setLngLat(coords[Math.floor(coords.length/2)])
|
// Calculate bounds
|
||||||
.setPopup(new maplibregl.Popup({ offset: 18 }).setHTML(popupHtml));
|
let bounds = allCoords.reduce((bounds, coord) => {
|
||||||
// marker.addTo(map); // Optionnel : afficher un marker au milieu
|
return [
|
||||||
}
|
[Math.min(bounds[0][0], coord[0]), Math.min(bounds[0][1], coord[1])],
|
||||||
|
[Math.max(bounds[1][0], coord[0]), Math.max(bounds[1][1], coord[1])]
|
||||||
|
];
|
||||||
|
}, [[Infinity, Infinity], [-Infinity, -Infinity]]);
|
||||||
|
|
||||||
|
// Fit map to bounds with padding
|
||||||
|
map.fitBounds(bounds, {
|
||||||
|
padding: 50,
|
||||||
|
maxZoom: 15
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create HTML for completion info and JOSM button
|
||||||
|
let completionHtml = `
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<b>Complétion maxspeed :</b> ${withMaxspeed} / ${total} (${completion}%)
|
||||||
|
<br><b>Tronçons sans maxspeed :</b> ${withoutMaxspeed}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button id="open-josm-button" class="btn btn-sm btn-primary" disabled>
|
||||||
|
<i class="bi bi-box-arrow-up-right"></i> Ouvrir dans JOSM
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.getElementById('speedlimit-completion').innerHTML = completionHtml;
|
||||||
|
|
||||||
|
// Store ways without maxspeed for JOSM
|
||||||
|
let waysWithoutMaxspeed = [];
|
||||||
|
data.elements.forEach(e => {
|
||||||
|
if (e.type === 'way' && e.tags && e.tags.highway && !e.tags.maxspeed) {
|
||||||
|
waysWithoutMaxspeed.push(e.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enable JOSM button if there are ways without maxspeed
|
||||||
|
const josmButton = document.getElementById('open-josm-button');
|
||||||
|
if (waysWithoutMaxspeed.length > 0) {
|
||||||
|
josmButton.disabled = false;
|
||||||
|
josmButton.addEventListener('click', function () {
|
||||||
|
const objects = waysWithoutMaxspeed.map(id => 'w' + id).join(',');
|
||||||
|
const josmUrl = `http://127.0.0.1:8111/load_object?objects=${objects}`;
|
||||||
|
window.open(josmUrl, '_blank');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// --- Carte des panneaux et feux ---
|
||||||
|
let map2 = new maplibregl.Map({
|
||||||
|
container: 'traffic-map',
|
||||||
|
style: 'https://api.maptiler.com/maps/streets/style.json?key={{ maptiler_token }}',
|
||||||
|
center: [2, 48],
|
||||||
|
zoom: 13
|
||||||
|
});
|
||||||
|
map2.addControl(new maplibregl.NavigationControl());
|
||||||
|
fetchOverpass(overpassTraffic).then(data => {
|
||||||
|
// Compteurs pour les différents types d'objets
|
||||||
|
let objectCounts = {
|
||||||
|
'traffic_signals': 0,
|
||||||
|
'traffic_sign': 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compteurs pour les différentes valeurs de traffic_sign
|
||||||
|
let trafficSignTypes = {};
|
||||||
|
|
||||||
|
data.elements.forEach(e => {
|
||||||
|
if (e.type === 'node') {
|
||||||
|
// Déterminer le type d'objet
|
||||||
|
let isTrafficSignal = e.tags && e.tags.highway === 'traffic_signals';
|
||||||
|
let objectType = isTrafficSignal ? 'traffic_signals' : 'traffic_sign';
|
||||||
|
|
||||||
|
// Incrémenter le compteur
|
||||||
|
objectCounts[objectType]++;
|
||||||
|
|
||||||
|
// Si c'est un panneau, compter son type
|
||||||
|
if (!isTrafficSignal && e.tags && e.tags.traffic_sign) {
|
||||||
|
// Un panneau peut avoir plusieurs types séparés par des points-virgules
|
||||||
|
let signTypes = e.tags.traffic_sign.split(';');
|
||||||
|
signTypes.forEach(type => {
|
||||||
|
type = type.trim();
|
||||||
|
if (type) {
|
||||||
|
trafficSignTypes[type] = (trafficSignTypes[type] || 0) + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Couleur et icône selon le type
|
||||||
|
let color = isTrafficSignal ? 'orange' : 'blue';
|
||||||
|
let icon = isTrafficSignal ? '🚦' : '🛑';
|
||||||
|
|
||||||
|
// Construire le HTML du popup avec toutes les propriétés
|
||||||
|
let popupHtml = `<b>${icon} ${isTrafficSignal ? 'Feu de circulation' : 'Panneau routier'}</b><br>ID: ${e.id}`;
|
||||||
|
|
||||||
|
// Ajouter toutes les propriétés du panneau
|
||||||
|
if (e.tags) {
|
||||||
|
Object.entries(e.tags).forEach(([key, value]) => {
|
||||||
|
popupHtml += `<br><b>${key}:</b> ${value}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Liens vers OSM, iD et JOSM
|
||||||
|
popupHtml += `<br><a href='https://www.openstreetmap.org/node/${e.id}' target='_blank'>OSM</a> | <a href='https://www.openstreetmap.org/edit?editor=id&node=${e.id}' target='_blank'>iD</a> | <a href='http://127.0.0.1:8111/load_object?objects=N${e.id}' target='_blank'>JOSM</a>`;
|
||||||
|
|
||||||
|
// Ajouter le marqueur à la carte
|
||||||
|
new maplibregl.Marker({color: color})
|
||||||
|
.setLngLat([e.lon, e.lat])
|
||||||
|
.setPopup(new maplibregl.Popup({offset: 18}).setHTML(popupHtml))
|
||||||
|
.addTo(map2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Générer le HTML pour la liste des objets trouvés
|
||||||
|
let totalObjects = objectCounts.traffic_signals + objectCounts.traffic_sign;
|
||||||
|
let objectsListHtml = `
|
||||||
|
<div class="list-group">
|
||||||
|
<div class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
<span>🚦 Feux de circulation</span>
|
||||||
|
<span class="badge bg-primary rounded-pill">${objectCounts.traffic_signals}</span>
|
||||||
|
</div>
|
||||||
|
<div class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
<span>🛑 Panneaux routiers</span>
|
||||||
|
<span class="badge bg-primary rounded-pill">${objectCounts.traffic_sign}</span>
|
||||||
|
</div>
|
||||||
|
<div class="list-group-item d-flex justify-content-between align-items-center fw-bold">
|
||||||
|
<span>Total</span>
|
||||||
|
<span class="badge bg-success rounded-pill">${totalObjects}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Si des types de panneaux ont été trouvés, les ajouter à la liste
|
||||||
|
if (Object.keys(trafficSignTypes).length > 0) {
|
||||||
|
objectsListHtml += `
|
||||||
|
<h6 class="mt-3">Types de panneaux</h6>
|
||||||
|
<div class="list-group">
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Trier les types de panneaux par nombre décroissant
|
||||||
|
let sortedTypes = Object.entries(trafficSignTypes)
|
||||||
|
.sort((a, b) => b[1] - a[1]);
|
||||||
|
|
||||||
|
sortedTypes.forEach(([type, count]) => {
|
||||||
|
objectsListHtml += `
|
||||||
|
<div class="list-group-item d-flex justify-content-between align-items-center small">
|
||||||
|
<span>${type}</span>
|
||||||
|
<span class="badge bg-secondary rounded-pill">${count}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
objectsListHtml += `</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mettre à jour la liste des objets
|
||||||
|
document.getElementById('traffic-objects-list').innerHTML = objectsListHtml;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
let completion = total > 0 ? Math.round(100 * withMaxspeed / total) : 0;
|
</script>
|
||||||
document.getElementById('speedlimit-completion').innerHTML = `<b>Complétion maxspeed :</b> ${withMaxspeed} / ${total} (${completion}%)`;
|
|
||||||
});
|
|
||||||
// --- Carte des panneaux et feux ---
|
|
||||||
let map2 = new maplibregl.Map({
|
|
||||||
container: 'traffic-map',
|
|
||||||
style: 'https://api.maptiler.com/maps/streets/style.json?key={{ maptiler_token }}',
|
|
||||||
center: [2, 48],
|
|
||||||
zoom: 13
|
|
||||||
});
|
|
||||||
map2.addControl(new maplibregl.NavigationControl());
|
|
||||||
fetchOverpass(overpassTraffic).then(data => {
|
|
||||||
data.elements.forEach(e => {
|
|
||||||
if (e.type === 'node') {
|
|
||||||
let color = e.tags && e.tags.highway === 'traffic_signals' ? 'orange' : 'blue';
|
|
||||||
let icon = e.tags && e.tags.highway === 'traffic_signals' ? '🚦' : '🛑';
|
|
||||||
let popupHtml = `<b>${icon} ${e.tags.highway === 'traffic_signals' ? 'Feu de circulation' : 'Panneau routier'}</b><br>ID: ${e.id}`;
|
|
||||||
if (e.tags.traffic_sign) popupHtml += `<br><b>traffic_sign:</b> ${e.tags.traffic_sign}`;
|
|
||||||
popupHtml += `<br><a href='https://www.openstreetmap.org/node/${e.id}' target='_blank'>OSM</a> | <a href='https://www.openstreetmap.org/edit?editor=id&node=${e.id}' target='_blank'>iD</a> | <a href='http://127.0.0.1:8111/load_object?objects=N${e.id}' target='_blank'>JOSM</a>`;
|
|
||||||
new maplibregl.Marker({ color: color })
|
|
||||||
.setLngLat([e.lon, e.lat])
|
|
||||||
.setPopup(new maplibregl.Popup({ offset: 18 }).setHTML(popupHtml))
|
|
||||||
.addTo(map2);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
Loading…
Add table
Add a link
Reference in a new issue