tyriaa commited on
Commit
1bebf37
·
1 Parent(s): 252dd51

Initial deployment

Browse files
Dockerfile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Utiliser une image Python officielle
2
+ FROM python:3.9-slim
3
+
4
+ # Définir le répertoire de travail
5
+ WORKDIR /app
6
+
7
+ # Copier les fichiers de dépendances
8
+ COPY requirements.txt .
9
+
10
+ # Installer les dépendances
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Copier le reste du code
14
+ COPY . .
15
+
16
+ # Exposer le port
17
+ EXPOSE 7860
18
+
19
+ # Commande pour démarrer l'application
20
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "app:app"]
app.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from flask import Flask, render_template, jsonify
4
+ import requests
5
+ import json
6
+ import re
7
+
8
+ app = Flask(__name__)
9
+
10
+ TOMTOM_API_KEY = "PVt4NaZSKKtziZi9DaqNAUzN4flNJnSo"
11
+ API_KEY = "3WsgOWybmrTiEwa3q8ZsvovvwPkrctnX"
12
+ BASE_URL_API = "https://prim.iledefrance-mobilites.fr/marketplace/v2/navitia/line_reports/physical_modes/physical_mode%3A"
13
+ CATEGORIES = {
14
+ "Métro": "Metro",
15
+ "RER": "RapidTransit",
16
+ "Bus": "Bus",
17
+ "Transilien": "LocalTrain",
18
+ "Tram": "Tramway"
19
+ }
20
+
21
+ # Fonction pour récupérer les perturbations
22
+ def fetch_disruptions(category, api_value):
23
+ url = f"{BASE_URL_API}{api_value}/line_reports?"
24
+ headers = {"accept": "application/json", "apiKey": API_KEY}
25
+
26
+ try:
27
+ response = requests.get(url, headers=headers, timeout=5)
28
+ response.raise_for_status()
29
+ data = response.json()
30
+ return data.get("disruptions", [])
31
+ except requests.exceptions.RequestException as e:
32
+ print(f"Erreur API pour {category}: {e}")
33
+ return []
34
+
35
+ # Normaliser le nom des lignes
36
+ def normalize_line_name(line_name):
37
+ line_name = line_name.lower()
38
+ line_name = re.sub(r"\b(ratp|sncf)\b", "", line_name)
39
+ line_name = re.sub(r"\s+", " ", line_name).strip()
40
+ return line_name.capitalize()
41
+
42
+ # Générer le HTML des perturbations
43
+ def generate_traffic_html():
44
+ all_disruptions = {}
45
+ for category, api_value in CATEGORIES.items():
46
+ disruptions = fetch_disruptions(category, api_value)
47
+ active_disruptions = [
48
+ {
49
+ "id": disruption.get("id"),
50
+ "cause": disruption.get("cause", "Inconnue"),
51
+ "severity": disruption.get("severity", {}).get("name", "N/A"),
52
+ "message": disruption["messages"][0]["text"] if disruption.get("messages") else "No message",
53
+ "impacted_lines": [
54
+ obj.get("pt_object", {}).get("name", "Unknown Line")
55
+ for obj in disruption.get("impacted_objects", [])
56
+ if obj.get("pt_object", {}).get("embedded_type") == "line"
57
+ ]
58
+ }
59
+ for disruption in disruptions if disruption.get("status") == "active"
60
+ ]
61
+ all_disruptions[category] = active_disruptions
62
+
63
+ # Récapitulatif des lignes impactées
64
+ summary_data = []
65
+ seen_lines = set()
66
+
67
+ for category, disruptions in all_disruptions.items():
68
+ for d in disruptions:
69
+ for line in d["impacted_lines"]:
70
+ norm_line = normalize_line_name(line)
71
+ if norm_line not in seen_lines:
72
+ summary_data.append({
73
+ "category": category,
74
+ "line": norm_line,
75
+ "cause": d["cause"],
76
+ "severity": d["severity"],
77
+ "message": d["message"]
78
+ })
79
+ seen_lines.add(norm_line)
80
+
81
+ return summary_data
82
+
83
+ @app.route('/')
84
+ def home():
85
+ return render_template('index.html')
86
+
87
+ @app.route('/api/incidents', methods=['GET'])
88
+ def get_incidents():
89
+ """
90
+ Récupère les incidents depuis l'API TomTom Traffic.
91
+ """
92
+ try:
93
+ # Zone géographique pour Paris
94
+ bounding_box = "1.79804455,48.52917947,2.88843762,49.24075760"
95
+ url = f"https://api.tomtom.com/traffic/services/5/incidentDetails?bbox={bounding_box}&fields=%7Bincidents%7Btype%2Cgeometry%7Btype%2Ccoordinates%7D%2Cproperties%7BiconCategory%7D%7D%7D&language=en-GB&categoryFilter=0%2C1%2C2%2C3%2C4%2C5%2C6%2C7%2C8%2C9%2C10%2C11%2C14&timeValidityFilter=present&key={TOMTOM_API_KEY}"
96
+
97
+ response = requests.get(url)
98
+ response.raise_for_status()
99
+
100
+ data = response.json()
101
+ incidents = data.get("incidents", [])
102
+ return jsonify({"status": "success", "data": incidents}), 200
103
+
104
+ except requests.exceptions.RequestException as e:
105
+ return jsonify({"status": "error", "message": str(e)}), 500
106
+
107
+ @app.route('/api/ratp_disruptions', methods=['GET'])
108
+ def ratp_disruptions():
109
+ data = generate_traffic_html()
110
+ return jsonify({"status": "success", "data": data})
111
+
112
+ @app.route('/api/traffic-disruptions', methods=['GET'])
113
+ def get_traffic_disruptions():
114
+ """
115
+ Récupère les perturbations de trafic pour tous les modes de transport.
116
+ """
117
+ try:
118
+ summary_data = generate_traffic_html()
119
+ return jsonify({"status": "success", "data": summary_data}), 200
120
+ except Exception as e:
121
+ return jsonify({"status": "error", "message": str(e)}), 500
122
+
123
+ if __name__ == '__main__':
124
+ app.run(debug=True)
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ Flask==2.3.3
2
+ requests==2.31.0
3
+ gunicorn==21.2.0
static/.DS_Store ADDED
Binary file (6.15 kB). View file
 
static/icons/1.svg ADDED
static/icons/10.svg ADDED
static/icons/11.svg ADDED
static/icons/14.svg ADDED
static/icons/2.svg ADDED
static/icons/3.svg ADDED
static/icons/4.svg ADDED
static/icons/5.svg ADDED
static/icons/6.svg ADDED
static/icons/7.svg ADDED
static/icons/8.svg ADDED
static/icons/9.svg ADDED
static/icons/Bus.svg ADDED
static/icons/Metro.png ADDED
static/icons/RER.svg ADDED
static/icons/Tram.png ADDED
static/icons/Transilien.png ADDED
static/js/traffic.js ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Variables globales
2
+ let disruptionsData = {};
3
+ let currentFilter = 'all';
4
+
5
+ // Fonction pour mettre à jour les lignes impactées
6
+ function updateImpactedLines() {
7
+ fetch('/api/ratp_disruptions')
8
+ .then(response => response.json())
9
+ .then(data => {
10
+ if (data.status === "success") {
11
+ disruptionsData = data.data;
12
+
13
+ // Réinitialiser tous les conteneurs de lignes impactées
14
+ document.querySelectorAll('.impacted-lines').forEach(container => {
15
+ container.innerHTML = '';
16
+ });
17
+
18
+ // Mettre à jour le tableau
19
+ updateDisruptionsTable(disruptionsData);
20
+
21
+ // Afficher les lignes impactées pour chaque catégorie
22
+ disruptionsData.forEach(item => {
23
+ const container = document.querySelector(`.impacted-lines[data-category="${item.category}"]`);
24
+ if (container) {
25
+ const lineElement = document.createElement('span');
26
+ lineElement.className = 'impacted-line';
27
+ lineElement.textContent = item.line;
28
+
29
+ // Ajouter l'événement de clic
30
+ lineElement.addEventListener('click', () => {
31
+ console.log('Clicked line:', item.line);
32
+ highlightTableRow(item);
33
+ });
34
+
35
+ container.appendChild(lineElement);
36
+ }
37
+ });
38
+
39
+ // Appliquer le filtre actuel
40
+ applyFilter(currentFilter);
41
+ }
42
+ })
43
+ .catch(error => console.error('Erreur lors de la récupération des perturbations:', error));
44
+ }
45
+
46
+ // Fonction pour mettre à jour le tableau des perturbations
47
+ function updateDisruptionsTable(disruptions) {
48
+ const tableBody = document.querySelector(".incident-table tbody");
49
+ if (!tableBody) {
50
+ console.error('Table body not found');
51
+ return;
52
+ }
53
+
54
+ tableBody.innerHTML = "";
55
+
56
+ disruptions.forEach(disruption => {
57
+ const row = document.createElement("tr");
58
+ row.setAttribute('data-line', disruption.line);
59
+ row.setAttribute('data-category', disruption.category);
60
+
61
+ row.innerHTML = `
62
+ <td>${disruption.line}</td>
63
+ <td>${disruption.cause}</td>
64
+ <td>${disruption.severity}</td>
65
+ <td>${disruption.message}</td>
66
+ `;
67
+
68
+ tableBody.appendChild(row);
69
+ });
70
+
71
+ // Réappliquer le filtre actuel
72
+ applyFilter(currentFilter);
73
+ }
74
+
75
+ // Fonction pour surligner une ligne dans le tableau
76
+ function highlightTableRow(disruption) {
77
+ console.log('Highlighting row for:', disruption);
78
+
79
+ // Retirer le surlignage précédent
80
+ document.querySelectorAll('tr.highlighted').forEach(row => {
81
+ row.classList.remove('highlighted');
82
+ });
83
+
84
+ // Appliquer le filtre correspondant à la catégorie
85
+ applyFilter(disruption.category);
86
+
87
+ // Trouver et surligner la nouvelle ligne
88
+ const row = document.querySelector(`tr[data-line="${disruption.line}"][data-category="${disruption.category}"]`);
89
+ if (row) {
90
+ console.log('Found row:', row);
91
+ row.classList.add('highlighted');
92
+ // Faire défiler jusqu'au tableau
93
+ row.scrollIntoView({ behavior: 'smooth', block: 'center' });
94
+ } else {
95
+ console.error('Row not found for:', disruption);
96
+ }
97
+ }
98
+
99
+ // Fonction pour appliquer un filtre
100
+ function applyFilter(category) {
101
+ currentFilter = category;
102
+ console.log('Applying filter:', category);
103
+
104
+ const rows = document.querySelectorAll('.incident-table tbody tr');
105
+ rows.forEach(row => {
106
+ const rowCategory = row.getAttribute('data-category');
107
+ if (category === 'all' || category === rowCategory) {
108
+ row.style.display = '';
109
+ } else {
110
+ row.style.display = 'none';
111
+ // Si la ligne est surlignée mais cachée, retirer le surlignage
112
+ if (row.classList.contains('highlighted')) {
113
+ row.classList.remove('highlighted');
114
+ }
115
+ }
116
+ });
117
+
118
+ // Mettre à jour l'état visuel des filtres
119
+ document.querySelectorAll('.filter li').forEach(li => {
120
+ if (li.getAttribute('data-category') === category) {
121
+ li.classList.add('active');
122
+ } else {
123
+ li.classList.remove('active');
124
+ }
125
+ });
126
+ }
127
+
128
+ // Initialisation au chargement de la page
129
+ document.addEventListener('DOMContentLoaded', function() {
130
+ console.log('Page loaded, initializing...');
131
+
132
+ // Initialiser les filtres
133
+ document.querySelectorAll('.filter li').forEach(li => {
134
+ li.addEventListener('click', function() {
135
+ const category = this.getAttribute('data-category');
136
+ applyFilter(category);
137
+ });
138
+ });
139
+
140
+ updateImpactedLines();
141
+ // Mettre à jour toutes les 60 secondes
142
+ setInterval(updateImpactedLines, 60000);
143
+ });
static/styles.css ADDED
@@ -0,0 +1,315 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Styles globaux */
2
+ body {
3
+ background-color: #1e1e1e; /* Fond noir */
4
+ color: #333; /* Texte principal */
5
+ margin: 0;
6
+ font-family: Arial, sans-serif;
7
+ }
8
+
9
+ /* Conteneur principal */
10
+ .container {
11
+ background-color: #dcdcdc;
12
+ margin: 20px auto; /* Réduit l'espace en haut et en bas */
13
+ padding: 20px;
14
+ max-width: 90%; /* Utiliser toute la largeur de l'écran */
15
+ border-radius: 8px;
16
+ }
17
+
18
+ /* Titre */
19
+ header {
20
+ text-align: center;
21
+ margin-bottom: 20px;
22
+ }
23
+
24
+ header h1 {
25
+ font-size: 28px;
26
+ color: black;
27
+ }
28
+
29
+ header .generated-time {
30
+ font-size: 14px;
31
+ color: gray;
32
+ }
33
+
34
+ /* Section principale */
35
+ main {
36
+ display: flex;
37
+ flex-direction: column;
38
+ gap: 20px;
39
+ margin: 20px auto;
40
+ }
41
+
42
+ /* Conteneur pour Trafic + MAP */
43
+ .traffic-map-container {
44
+ display: flex; /* Aligne Trafic + MAP horizontalement */
45
+ gap: 20px; /* Espacement entre les deux sections */
46
+ margin-bottom: 20px;
47
+ }
48
+
49
+ /* Section Gauche : Trafic */
50
+ .traffic-info {
51
+ flex: 1; /* Prend 1 portion de l'espace */
52
+ background: white;
53
+ padding: 20px;
54
+ border-radius: 8px;
55
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
56
+ }
57
+
58
+ .traffic-info h2 {
59
+ font-size: 18px;
60
+ margin-bottom: 10px;
61
+ }
62
+
63
+ .traffic-items {
64
+ display: flex;
65
+ flex-direction: column;
66
+ gap: 15px;
67
+ margin-top: 20px;
68
+ }
69
+
70
+ .icon-item {
71
+ display: flex;
72
+ flex-direction: row;
73
+ align-items: center;
74
+ gap: 20px;
75
+ padding: 10px;
76
+ background-color: #f5f5f5;
77
+ border-radius: 8px;
78
+ }
79
+
80
+ .icon-img {
81
+ width: 32px; /* Ajuste la taille de l'icône */
82
+ height: 32px;
83
+ }
84
+
85
+ .transport-icon {
86
+ display: flex;
87
+ align-items: center;
88
+ gap: 10px;
89
+ min-width: 120px;
90
+ }
91
+
92
+ /* Styles pour la carte */
93
+ #map {
94
+ width: 100%;
95
+ height: 400px; /* Taille de la carte */
96
+ border-radius: 8px;
97
+ }
98
+
99
+ .map-section {
100
+ flex: 1;
101
+ padding: 0;
102
+ }
103
+
104
+ /* Section Filtres et Tableau */
105
+ .filter-table {
106
+ display: flex;
107
+ gap: 20px;
108
+ background-color: #f0f0f0;
109
+ padding: 20px;
110
+ border-radius: 8px;
111
+ }
112
+
113
+ /* Filtres */
114
+ .filter {
115
+ flex: 1;
116
+ background-color: #dcdcdc;
117
+ padding: 10px 20px;
118
+ border-radius: 8px;
119
+ }
120
+
121
+ .filter h3 {
122
+ font-size: 18px;
123
+ margin-bottom: 10px;
124
+ }
125
+
126
+ .filter ul {
127
+ list-style-type: none;
128
+ padding: 0;
129
+ }
130
+
131
+ .filter li {
132
+ cursor: pointer;
133
+ padding: 5px 10px;
134
+ margin: 2px 0;
135
+ border-radius: 4px;
136
+ transition: background-color 0.2s;
137
+ }
138
+
139
+ .filter li:hover {
140
+ background-color: #f0f0f0;
141
+ }
142
+
143
+ .filter li.active {
144
+ background-color: #007bff;
145
+ color: white;
146
+ }
147
+
148
+ /* Tableau des incidents */
149
+ .incident-table {
150
+ flex: 3;
151
+ background-color: white;
152
+ padding: 10px;
153
+ border-radius: 8px;
154
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
155
+ }
156
+
157
+ table {
158
+ width: 100%;
159
+ border-collapse: collapse;
160
+ }
161
+
162
+ th, td {
163
+ border: 1px solid #ccc;
164
+ padding: 10px;
165
+ text-align: left;
166
+ }
167
+
168
+ th {
169
+ background-color: #e0e0e0;
170
+ font-weight: bold;
171
+ }
172
+
173
+ /* Style pour la ligne sélectionnée dans le tableau */
174
+ tr.highlighted {
175
+ background-color: #fff3cd !important;
176
+ transition: background-color 0.3s ease;
177
+ }
178
+
179
+ tr.highlighted td {
180
+ animation: highlight-pulse 2s infinite;
181
+ }
182
+
183
+ @keyframes highlight-pulse {
184
+ 0% { background-color: #fff3cd; }
185
+ 50% { background-color: #ffe69c; }
186
+ 100% { background-color: #fff3cd; }
187
+ }
188
+
189
+ /* Styles for impacted lines display */
190
+ .impacted-lines {
191
+ display: flex;
192
+ flex-wrap: wrap;
193
+ gap: 5px;
194
+ align-items: center;
195
+ margin-top: 5px;
196
+ font-size: 0.9em;
197
+ min-height: 20px;
198
+ }
199
+
200
+ .impacted-line {
201
+ display: inline-block;
202
+ padding: 2px 6px;
203
+ border-radius: 4px;
204
+ background-color: #ff4444;
205
+ color: white;
206
+ font-weight: bold;
207
+ font-size: 0.9em;
208
+ cursor: help;
209
+ position: relative;
210
+ }
211
+
212
+ .impacted-line[data-tooltip] {
213
+ position: relative;
214
+ }
215
+
216
+ .impacted-line[data-tooltip]::after {
217
+ content: attr(data-tooltip);
218
+ position: absolute;
219
+ bottom: 100%;
220
+ left: 50%;
221
+ transform: translateX(-50%);
222
+ padding: 8px 12px;
223
+ background-color: rgba(0, 0, 0, 0.8);
224
+ color: white;
225
+ border-radius: 4px;
226
+ font-size: 14px;
227
+ white-space: nowrap;
228
+ z-index: 1000;
229
+ visibility: hidden;
230
+ opacity: 0;
231
+ transition: opacity 0.2s ease-in-out;
232
+ margin-bottom: 5px;
233
+ max-width: 300px;
234
+ white-space: normal;
235
+ text-align: center;
236
+ }
237
+
238
+ .impacted-line[data-tooltip]:hover::after {
239
+ visibility: visible;
240
+ opacity: 1;
241
+ }
242
+
243
+ .impacted-line[data-tooltip]::before {
244
+ content: '';
245
+ position: absolute;
246
+ bottom: 100%;
247
+ left: 50%;
248
+ transform: translateX(-50%);
249
+ border: 6px solid transparent;
250
+ border-top-color: rgba(0, 0, 0, 0.8);
251
+ visibility: hidden;
252
+ opacity: 0;
253
+ transition: opacity 0.2s ease-in-out;
254
+ margin-bottom: -6px;
255
+ }
256
+
257
+ .impacted-line[data-tooltip]:hover::before {
258
+ visibility: visible;
259
+ opacity: 1;
260
+ }
261
+
262
+ .impacted-line:hover {
263
+ background-color: #ff6666;
264
+ }
265
+
266
+ /* Styles pour la modale */
267
+ .modal {
268
+ display: none;
269
+ position: fixed;
270
+ z-index: 1000;
271
+ left: 0;
272
+ top: 0;
273
+ width: 100%;
274
+ height: 100%;
275
+ background-color: rgba(0, 0, 0, 0.5);
276
+ }
277
+
278
+ .modal-content {
279
+ background-color: #fefefe;
280
+ margin: 15% auto;
281
+ padding: 20px;
282
+ border: 1px solid #888;
283
+ width: 80%;
284
+ max-width: 600px;
285
+ border-radius: 8px;
286
+ position: relative;
287
+ }
288
+
289
+ .close-modal {
290
+ position: absolute;
291
+ right: 20px;
292
+ top: 10px;
293
+ color: #aaa;
294
+ font-size: 28px;
295
+ font-weight: bold;
296
+ cursor: pointer;
297
+ }
298
+
299
+ .close-modal:hover {
300
+ color: #555;
301
+ }
302
+
303
+ .modal h3 {
304
+ margin-top: 0;
305
+ color: #333;
306
+ margin-bottom: 20px;
307
+ }
308
+
309
+ .modal-line-info,
310
+ .modal-cause,
311
+ .modal-message,
312
+ .modal-severity {
313
+ margin-bottom: 15px;
314
+ line-height: 1.4;
315
+ }
templates/index.html ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Carte des Incidents à Paris</title>
7
+ <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css">
8
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
9
+ </head>
10
+ <body>
11
+ <div class="container">
12
+ <!-- Titre -->
13
+ <header>
14
+ <h1>Carte des Incidents à Paris</h1>
15
+ <p class="generated-time">Cette page a été générée à : <span id="pageLoadTime"></span></p>
16
+ </header>
17
+
18
+ <!-- Contenu principal -->
19
+ <main>
20
+ <!-- Section avec Trafic + MAP alignés horizontalement -->
21
+ <section class="traffic-map-container">
22
+ <!-- Section Gauche : Trafic -->
23
+ <section class="traffic-info">
24
+ <h2>Un oeil sur le trafic</h2>
25
+ <div class="traffic-items">
26
+ <div class="icon-item">
27
+ <div class="transport-icon">
28
+ <img src="{{ url_for('static', filename='icons/RER.svg') }}" alt="RER" class="icon-img"> RER
29
+ </div>
30
+ <div class="impacted-lines" data-category="RER"></div>
31
+ </div>
32
+ <div class="icon-item">
33
+ <div class="transport-icon">
34
+ <img src="{{ url_for('static', filename='icons/Metro.png') }}" alt="Métro" class="icon-img"> Métro
35
+ </div>
36
+ <div class="impacted-lines" data-category="Métro"></div>
37
+ </div>
38
+ <div class="icon-item">
39
+ <div class="transport-icon">
40
+ <img src="{{ url_for('static', filename='icons/Bus.svg') }}" alt="Bus" class="icon-img"> Bus
41
+ </div>
42
+ <div class="impacted-lines" data-category="Bus"></div>
43
+ </div>
44
+ <div class="icon-item">
45
+ <div class="transport-icon">
46
+ <img src="{{ url_for('static', filename='icons/Transilien.png') }}" alt="Transilien" class="icon-img"> Transilien
47
+ </div>
48
+ <div class="impacted-lines" data-category="Transilien"></div>
49
+ </div>
50
+ <div class="icon-item">
51
+ <div class="transport-icon">
52
+ <img src="{{ url_for('static', filename='icons/Tram.png') }}" alt="Tramway" class="icon-img"> Tramway
53
+ </div>
54
+ <div class="impacted-lines" data-category="Tram"></div>
55
+ </div>
56
+ </div>
57
+ </section>
58
+
59
+ <!-- Section Droite : Carte -->
60
+ <section class="map-section">
61
+ <div id="map"></div>
62
+ </section>
63
+ </section>
64
+
65
+ <!-- Section Filtres et Tableau -->
66
+ <section class="filter-table">
67
+ <div class="filter">
68
+ <h3>Filtre</h3>
69
+ <ul>
70
+ <li data-category="all">Tous</li>
71
+ <li data-category="RER">RER</li>
72
+ <li data-category="Métro">Métro</li>
73
+ <li data-category="Bus">Bus</li>
74
+ <li data-category="Transilien">Transilien</li>
75
+ <li data-category="Tram">Tramway</li>
76
+ </ul>
77
+ </div>
78
+
79
+ <div class="incident-table">
80
+ <table>
81
+ <thead>
82
+ <tr>
83
+ <th>Ligne</th>
84
+ <th>Cause</th>
85
+ <th>Gravité</th>
86
+ <th>Message</th>
87
+ </tr>
88
+ </thead>
89
+ <tbody>
90
+ <!-- Les perturbations sont insérées ici dynamiquement -->
91
+ </tbody>
92
+ </table>
93
+ </div>
94
+ </section>
95
+ </main>
96
+ </div>
97
+
98
+ <!-- JavaScript -->
99
+ <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
100
+ <script src="{{ url_for('static', filename='js/traffic.js') }}"></script>
101
+ <script>
102
+ // Initialiser la carte
103
+ const map = L.map('map').setView([48.8566, 2.3522], 12);
104
+
105
+ // Ajouter une couche de tuiles
106
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
107
+ attribution: '© Contributeurs OpenStreetMap'
108
+ }).addTo(map);
109
+
110
+ // Récupérer les incidents depuis l'API TomTom
111
+ fetch('/api/incidents')
112
+ .then(response => response.json())
113
+ .then(data => {
114
+ if (data.status === "success") {
115
+ const incidents = data.data;
116
+
117
+ incidents.forEach(incident => {
118
+ const coordinates = incident.geometry.coordinates;
119
+ const type = incident.geometry.type;
120
+
121
+ if (type === "Point") {
122
+ const [lon, lat] = coordinates;
123
+ const iconCategory = incident.properties.iconCategory;
124
+
125
+ const customIcon = L.icon({
126
+ iconUrl: `/static/icons/${iconCategory}.svg`,
127
+ iconSize: [72, 72]
128
+ });
129
+
130
+ const categoryDescriptions = {
131
+ 1: "Accident",
132
+ 6: "Embouteillage",
133
+ 9: "Travaux routiers",
134
+ 14: "Véhicule en panne"
135
+ };
136
+
137
+ const description = categoryDescriptions[iconCategory] || "Incident inconnu";
138
+
139
+ L.marker([lat, lon], { icon: customIcon })
140
+ .addTo(map)
141
+ .bindPopup(`<b>Incident</b><br>${description}`);
142
+ }
143
+ });
144
+ }
145
+ });
146
+
147
+ // Mettre à jour l'heure de génération
148
+ document.getElementById('pageLoadTime').textContent = new Date().toLocaleString();
149
+ </script>
150
+ </body>
151
+ </html>
templates/index_new.html ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Carte des Incidents à Paris</title>
7
+ <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css">
8
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
9
+ </head>
10
+ <body>
11
+ <div class="container">
12
+ <!-- Titre -->
13
+ <header>
14
+ <h1>Carte des Incidents à Paris</h1>
15
+ <p class="generated-time">Cette page a été générée à : <span id="pageLoadTime"></span></p>
16
+ </header>
17
+
18
+ <!-- Contenu principal -->
19
+ <main>
20
+ <!-- Section avec Trafic + MAP alignés horizontalement -->
21
+ <section class="traffic-map-container">
22
+ <!-- Section Gauche : Trafic -->
23
+ <section class="traffic-info">
24
+ <h2>Un oeil sur le trafic</h2>
25
+ <div class="traffic-items">
26
+ <div class="icon-item">
27
+ <div class="transport-icon">
28
+ <img src="{{ url_for('static', filename='icons/RER.svg') }}" alt="RER" class="icon-img"> RER
29
+ </div>
30
+ <div class="impacted-lines" data-category="RER"></div>
31
+ </div>
32
+ <div class="icon-item">
33
+ <div class="transport-icon">
34
+ <img src="{{ url_for('static', filename='icons/Metro.png') }}" alt="Métro" class="icon-img"> Métro
35
+ </div>
36
+ <div class="impacted-lines" data-category="Métro"></div>
37
+ </div>
38
+ <div class="icon-item">
39
+ <div class="transport-icon">
40
+ <img src="{{ url_for('static', filename='icons/Bus.svg') }}" alt="Bus" class="icon-img"> Bus
41
+ </div>
42
+ <div class="impacted-lines" data-category="Bus"></div>
43
+ </div>
44
+ <div class="icon-item">
45
+ <div class="transport-icon">
46
+ <img src="{{ url_for('static', filename='icons/Transilien.png') }}" alt="Transilien" class="icon-img"> Transilien
47
+ </div>
48
+ <div class="impacted-lines" data-category="Transilien"></div>
49
+ </div>
50
+ <div class="icon-item">
51
+ <div class="transport-icon">
52
+ <img src="{{ url_for('static', filename='icons/Tram.png') }}" alt="Tramway" class="icon-img"> Tramway
53
+ </div>
54
+ <div class="impacted-lines" data-category="Tram"></div>
55
+ </div>
56
+ </div>
57
+ </section>
58
+
59
+ <!-- Section Droite : Carte -->
60
+ <section class="map-section">
61
+ <div id="map"></div>
62
+ </section>
63
+ </section>
64
+
65
+ <!-- Section Filtres et Tableau -->
66
+ <section class="filter-table">
67
+ <div class="filter">
68
+ <h3>Filtre</h3>
69
+ <ul>
70
+ <li data-category="all">Tous</li>
71
+ <li data-category="RER">RER</li>
72
+ <li data-category="Métro">Métro</li>
73
+ <li data-category="Bus">Bus</li>
74
+ <li data-category="Transilien">Transilien</li>
75
+ <li data-category="Tramway">Tramway</li>
76
+ </ul>
77
+ </div>
78
+
79
+ <div class="incident-table">
80
+ <table>
81
+ <thead>
82
+ <tr>
83
+ <th>Ligne</th>
84
+ <th>Cause</th>
85
+ <th>Gravité</th>
86
+ <th>Message</th>
87
+ </tr>
88
+ </thead>
89
+ <tbody>
90
+ <!-- Les perturbations sont insérées ici dynamiquement -->
91
+ </tbody>
92
+ </table>
93
+ </div>
94
+ </section>
95
+ </main>
96
+ </div>
97
+
98
+ <!-- Modal pour les détails des perturbations -->
99
+ <div id="disruptionModal" class="modal">
100
+ <div class="modal-content">
101
+ <span class="close-modal">&times;</span>
102
+ <h3>Détails de la perturbation</h3>
103
+ <div class="modal-line-info">
104
+ <strong>Ligne: </strong><span id="modalLine"></span>
105
+ </div>
106
+ <div class="modal-cause">
107
+ <strong>Cause: </strong><span id="modalCause"></span>
108
+ </div>
109
+ <div class="modal-message">
110
+ <strong>Message: </strong><span id="modalMessage"></span>
111
+ </div>
112
+ <div class="modal-severity">
113
+ <strong>Gravité: </strong><span id="modalSeverity"></span>
114
+ </div>
115
+ </div>
116
+ </div>
117
+
118
+ <!-- JavaScript -->
119
+ <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
120
+ <script src="{{ url_for('static', filename='js/traffic.js') }}"></script>
121
+ <script>
122
+ // Initialiser la carte
123
+ const map = L.map('map').setView([48.8566, 2.3522], 12);
124
+
125
+ // Ajouter une couche de tuiles
126
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
127
+ attribution: '© Contributeurs OpenStreetMap'
128
+ }).addTo(map);
129
+
130
+ // Récupérer les incidents depuis l'API TomTom
131
+ fetch('/api/incidents')
132
+ .then(response => response.json())
133
+ .then(data => {
134
+ if (data.status === "success") {
135
+ const incidents = data.data;
136
+
137
+ incidents.forEach(incident => {
138
+ const coordinates = incident.geometry.coordinates;
139
+ const type = incident.geometry.type;
140
+
141
+ if (type === "Point") {
142
+ const [lon, lat] = coordinates;
143
+ const iconCategory = incident.properties.iconCategory;
144
+
145
+ const customIcon = L.icon({
146
+ iconUrl: `/static/icons/${iconCategory}.svg`,
147
+ iconSize: [72, 72]
148
+ });
149
+
150
+ const categoryDescriptions = {
151
+ 1: "Accident",
152
+ 6: "Embouteillage",
153
+ 9: "Travaux routiers",
154
+ 14: "Véhicule en panne"
155
+ };
156
+
157
+ const description = categoryDescriptions[iconCategory] || "Incident inconnu";
158
+
159
+ L.marker([lat, lon], { icon: customIcon })
160
+ .addTo(map)
161
+ .bindPopup(`<b>Incident</b><br>${description}`);
162
+ }
163
+ });
164
+ }
165
+ });
166
+
167
+ // Mettre à jour l'heure de génération
168
+ document.getElementById('pageLoadTime').textContent = new Date().toLocaleString();
169
+ </script>
170
+ </body>
171
+ </html>