| (9 révisions intermédiaires par le même utilisateur non affichées) | |||
| Ligne 1 : | Ligne 1 : | ||
{{Tuto Details | {{Tuto Details | ||
| + | |Main_Picture=Mobi_BOX_IMG_0412.jpeg | ||
|Licences=Attribution (CC-BY) | |Licences=Attribution (CC-BY) | ||
| − | |Description= | + | |Description=Un dispositif fournissant des informations en temps réel sur la pertinence d’utiliser une voiture ou les transports en commun, en évaluant la qualité de l’air, les niveaux de pollution atmosphérique ainsi que la disponibilité des différents moyens de transport dans la zone |
|Disciplines scientifiques=Arduino, Computing, Electricity, Mechanics | |Disciplines scientifiques=Arduino, Computing, Electricity, Mechanics | ||
|Difficulty=Expert | |Difficulty=Expert | ||
| Ligne 21 : | Ligne 22 : | ||
|Item=LED | |Item=LED | ||
}}{{ItemList | }}{{ItemList | ||
| − | |Item=Bois | + | |Item=Boite en carton |
| + | }}{{ItemList | ||
| + | |Item=ESP32 | ||
| + | }} | ||
| + | }} | ||
| + | {{Tuto Step | ||
| + | |Step_Title=Comprendre le terme ( Mobilité de transport commun) | ||
| + | |Step_Content=Chaque participant partage sa compréhension du thème. Toutes les idées et les mots-clés sont ensuite notés et organisés sous forme de schéma. | ||
| + | |Step_Picture_00=Mobi_BOX_IMG_1827.jpeg | ||
| + | |Step_Picture_01=Mobi_BOX_IMG_1829.jpeg | ||
| + | }} | ||
| + | {{Tuto Step | ||
| + | |Step_Title=Recherche dans les sources disponibles | ||
| + | |Step_Content=Analyse des sources disponibles sur les sites de TBM et de Bordeaux Métropole pour identifier les bénéfices environnementaux du tramway et du réseau de bus. | ||
| + | |Step_Picture_00=Mobi_BOX_WhatsApp_Image_2026-05-30_at_13.55.11.jpeg | ||
| + | }} | ||
| + | {{Tuto Step | ||
| + | |Step_Title=Élaboration d’un schéma préliminaire | ||
| + | |Step_Content=Conception d’une machine capable d’analyser la qualité de l’air et le trafic, afin de proposer le meilleur moyen de transport entre bus, tram, voiture ou taxi. | ||
| + | |Step_Picture_00=Mobi_BOX_IMG_1832.jpeg | ||
| + | |Step_Picture_01=Mobi_BOX_Capture_d_e_cran_2025-12-05_a_10.36.02.png | ||
| + | }} | ||
| + | {{Tuto Step | ||
| + | |Step_Title=Choisir les matériaux pour la maquette | ||
| + | |Step_Content=Bois, LED, papier transparent, éléments imprimés en 3D, ainsi que des cartes électroniques(Arduino ) pour la rotation et l’éclairage des LED. | ||
| + | |Step_Picture_00=Mobi_BOX_93993C_zoo.jpg | ||
| + | |Step_Picture_01=Mobi_BOX_IMG_0413.jpeg | ||
| + | |Step_Picture_02=Mobi_BOX_IMG_0333.mov | ||
| + | |Step_Picture_03=Mobi_BOX_IMG_0326.jpeg | ||
| + | |Step_Picture_04=Mobi_BOX_WhatsApp_Image_2026-05-30_at_14.12.33.jpeg | ||
| + | }} | ||
| + | {{Tuto Step | ||
| + | |Step_Title=Programmation sur Python | ||
| + | |Step_Content=import requests | ||
| + | |||
| + | from bs4 import BeautifulSoup | ||
| + | |||
| + | class AQIRepository: | ||
| + | |||
| + | URL = "<nowiki>https://www.atmo-nouvelleaquitaine.org/air-commune/Bordeaux/33063/indice-atmo?date=2025-10-14</nowiki>" | ||
| + | |||
| + | def __init__(self): | ||
| + | |||
| + | self.cat_to_conc = { | ||
| + | |||
| + | 'Bon': {'PM2.5': 5.0, 'PM10': 15.0, 'O3': 0.040, 'NO2': 20.0, 'SO2': 5.0}, | ||
| + | |||
| + | 'Moyen': {'PM2.5': 20.0, 'PM10': 40.0, 'O3': 0.060, 'NO2': 60.0, 'SO2': 20.0}, | ||
| + | |||
| + | 'Mauvais':{'PM2.5': 40.0, 'PM10': 100.0,'O3': 0.100, 'NO2': 150.0,'SO2': 100.0}, | ||
| + | |||
| + | 'Très mauvais':{'PM2.5':120.0,'PM10':300.0,'O3':0.180,'NO2':800.0,'SO2':500.0} | ||
| + | |||
| + | } | ||
| + | |||
| + | self.breakpoints = { | ||
| + | |||
| + | 'PM2.5': [(0.0,12.0,0,50),(12.1,35.4,51,100),(35.5,55.4,101,150),(55.5,150.4,151,200)], | ||
| + | |||
| + | 'PM10': [(0,54,0,50),(55,154,51,100),(155,254,101,150),(255,354,151,200)], | ||
| + | |||
| + | 'O3': [(0.000,0.054,0,50),(0.055,0.070,51,100),(0.071,0.085,101,150)], | ||
| + | |||
| + | 'NO2': [(0,53,0,50),(54,100,51,100),(101,360,101,150),(361,649,151,200)], | ||
| + | |||
| + | 'SO2': [(0,35,0,50),(36,75,51,100),(76,185,101,150),(186,304,151,200)], | ||
| + | |||
| + | } | ||
| + | |||
| + | def linear_scale(self, C, C_low, C_high, I_low, I_high): | ||
| + | |||
| + | return round((I_high - I_low) / (C_high - C_low) * (C - C_low) + I_low) | ||
| + | |||
| + | def find_aqi_subindex(self, C, pollutant): | ||
| + | |||
| + | for (C_low, C_high, I_low, I_high) in self.breakpoints[pollutant]: | ||
| + | |||
| + | if C_low <= C <= C_high: | ||
| + | |||
| + | return self.linear_scale(C, C_low, C_high, I_low, I_high) | ||
| + | |||
| + | return None | ||
| + | |||
| + | def scrape_categories(self): | ||
| + | |||
| + | response = requests.get(self.URL, timeout=10) | ||
| + | |||
| + | soup = BeautifulSoup(response.text, "html.parser") | ||
| + | |||
| + | categories = {} | ||
| + | |||
| + | blocks = soup.select(".c-indice-polluant") | ||
| + | |||
| + | for block in blocks: | ||
| + | |||
| + | title_tag = block.select_one(".c-indice-polluant-title span") | ||
| + | |||
| + | cat_tag = block.select_one(".home-map-legend-item span") | ||
| + | |||
| + | if title_tag and cat_tag: | ||
| + | |||
| + | name = title_tag.get_text(strip=True).replace("₂", "2").replace("₃", "3") | ||
| + | |||
| + | cat = cat_tag.get_text(strip=True) | ||
| + | |||
| + | categories[name] = cat | ||
| + | |||
| + | return categories | ||
| + | |||
| + | def compute_aqi(self, categories): | ||
| + | |||
| + | concentrations = {k: self.cat_to_conc[v][k] for k, v in categories.items() if k in self.cat_to_conc[v]} | ||
| + | |||
| + | subindices = {k: self.find_aqi_subindex(concentrations[k], k) for k in concentrations} | ||
| + | |||
| + | aqi_global = max(v for v in subindices.values() if v is not None) | ||
| + | |||
| + | return aqi_global, subindices | ||
| + | |||
| + | import requests | ||
| + | |||
| + | class MeteoRepository: | ||
| + | |||
| + | def __init__(self, latitude=44.8378, longitude=-0.5792): | ||
| + | |||
| + | self.latitude = latitude | ||
| + | |||
| + | self.longitude = longitude | ||
| + | |||
| + | self.api_url = ( | ||
| + | |||
| + | "<nowiki>https://api.open-meteo.com/v1/forecast</nowiki>?" | ||
| + | |||
| + | f"latitude={self.latitude}&longitude={self.longitude}" | ||
| + | |||
| + | "&hourly=temperature_2m,precipitation,weathercode" | ||
| + | |||
| + | "¤t_weather=true&timezone=Europe%2FParis" | ||
| + | |||
| + | ) | ||
| + | |||
| + | def get_weather(self): | ||
| + | |||
| + | response = requests.get(self.api_url, timeout=10) | ||
| + | |||
| + | data = response.json() | ||
| + | |||
| + | return data.get("current_weather", {}) | ||
| + | |||
| + | def compute_weather_score(self, weather): | ||
| + | |||
| + | wmo_scores = { | ||
| + | |||
| + | 0: 100, 1: 90, 2: 80, 3: 70, 45: 60, 48: 55, | ||
| + | |||
| + | 51: 50, 53: 45, 55: 40, 61: 35, 63: 25, 65: 15, | ||
| + | |||
| + | 71: 25, 73: 15, 75: 5, 95: 10, 99: 0, | ||
| + | |||
| + | } | ||
| + | |||
| + | temp = weather.get("temperature", 20) | ||
| + | |||
| + | wind = weather.get("windspeed", 10) | ||
| + | |||
| + | code = weather.get("weathercode", 0) | ||
| + | |||
| + | base_score = wmo_scores.get(code, 60) | ||
| + | |||
| + | if temp < 0: | ||
| + | |||
| + | temp_penalty = 30 | ||
| + | |||
| + | elif temp < 10: | ||
| + | |||
| + | temp_penalty = 15 | ||
| + | |||
| + | elif temp > 30: | ||
| + | |||
| + | temp_penalty = 20 | ||
| + | |||
| + | else: | ||
| + | |||
| + | temp_penalty = 0 | ||
| + | |||
| + | wind_penalty = 0 | ||
| + | |||
| + | if wind > 40: | ||
| + | |||
| + | wind_penalty = 20 | ||
| + | |||
| + | elif wind > 25: | ||
| + | |||
| + | wind_penalty = 10 | ||
| + | |||
| + | score = base_score - temp_penalty - wind_penalty | ||
| + | |||
| + | return round(max(0, min(100, score))) | ||
| + | |||
| + | import requests | ||
| + | |||
| + | import json | ||
| + | |||
| + | class TrafficRepository: | ||
| + | |||
| + | BASE_URL = "<nowiki>https://data.bordeaux-metropole.fr/geojson</nowiki>" | ||
| + | |||
| + | def __init__(self, api_key="2HH8S7FKPP", typename="ci_trafi_l"): | ||
| + | |||
| + | self.api_key = api_key | ||
| + | |||
| + | self.typename = typename | ||
| + | |||
| + | def get_traffic_by_gid(self, gid=2215): | ||
| + | |||
| + | params = { | ||
| + | |||
| + | "key": self.api_key, | ||
| + | |||
| + | "typename": self.typename, | ||
| + | |||
| + | "filter": json.dumps({"gid": gid}), | ||
| + | |||
| + | } | ||
| + | |||
| + | response = requests.get(self.BASE_URL, params=params, timeout=10) | ||
| + | |||
| + | if response.status_code != 200: | ||
| + | |||
| + | raise Exception(f"Erreur API {response.status_code} : {response.text}") | ||
| + | |||
| + | return response.json() | ||
| + | |||
| + | def compute_traffic_score(self, traffic_data): | ||
| + | |||
| + | features = traffic_data.get("features", []) | ||
| + | |||
| + | if not features: | ||
| + | |||
| + | return 50 | ||
| + | |||
| + | props = features[0].get("properties", {}) | ||
| + | |||
| + | etat = props.get("etat_trafic") or props.get("etat") or "INCONNU" | ||
| + | |||
| + | mapping = {"FLUIDE": 100, "RALENTI": 70, "EMBOUTEILLE": 30, "INCONNU": 50} | ||
| + | |||
| + | return mapping.get(etat.upper(), 50) | ||
| + | |||
| + | import requests | ||
| + | |||
| + | import json | ||
| + | |||
| + | from datetime import datetime, timezone | ||
| + | |||
| + | class TbmRepository: | ||
| + | |||
| + | BASE_URL = "<nowiki>https://data.bordeaux-metropole.fr/geojson</nowiki>" | ||
| + | |||
| + | def __init__(self, api_key="2HH8S7FKPP", arret_id=4037): | ||
| + | |||
| + | self.api_key = api_key | ||
| + | |||
| + | self.arret_id = arret_id | ||
| + | |||
| + | def get_next_tram(self, max_results=100): | ||
| + | |||
| + | params = { | ||
| + | |||
| + | "key": self.api_key, | ||
| + | |||
| + | "typename": "sv_horai_a", | ||
| + | |||
| + | "filter": json.dumps({"rs_sv_arret_p": self.arret_id}), | ||
| + | |||
| + | "orderby": json.dumps(["hor_theo"]), | ||
| + | |||
| + | "maxfeatures": max_results | ||
| + | |||
| + | } | ||
| + | |||
| + | response = requests.get(self.BASE_URL, params=params) | ||
| + | |||
| + | response.raise_for_status() | ||
| + | |||
| + | data = response.json() | ||
| + | |||
| + | now = datetime.now(timezone.utc) | ||
| + | |||
| + | for feature in data.get("features", []): | ||
| + | |||
| + | props = feature.get("properties", {}) | ||
| + | |||
| + | hor_theo = props.get("hor_theo") | ||
| + | |||
| + | if not hor_theo: | ||
| + | |||
| + | continue | ||
| + | |||
| + | try: | ||
| + | |||
| + | tram_time = datetime.fromisoformat(hor_theo) | ||
| + | |||
| + | except ValueError: | ||
| + | |||
| + | continue | ||
| + | |||
| + | if tram_time > now: | ||
| + | |||
| + | diff_sec = int((tram_time - now).total_seconds()) | ||
| + | |||
| + | return {"heure": tram_time, "seconds": diff_sec, "etat": props.get("etat"), "type": props.get("type")} | ||
| + | |||
| + | return None | ||
| + | |||
| + | from AQIRepository import AQIRepository | ||
| + | |||
| + | from MeteoRepository import MeteoRepository | ||
| + | |||
| + | from TrafficRepository import TrafficRepository | ||
| + | |||
| + | from TramRepository import TbmRepository | ||
| + | |||
| + | from fastapi import FastAPI | ||
| + | |||
| + | app = FastAPI() | ||
| + | |||
| + | @app.get("/") | ||
| + | |||
| + | def read_root(): | ||
| + | |||
| + | longitude = -0.5935101 | ||
| + | |||
| + | latitude = 44.8538665 | ||
| + | |||
| + | borneTraficGID = 2215 | ||
| + | |||
| + | Aqirepo = AQIRepository() | ||
| + | |||
| + | categories = Aqirepo.scrape_categories() | ||
| + | |||
| + | aqi, details = Aqirepo.compute_aqi(categories) | ||
| + | |||
| + | meteo = MeteoRepository(latitude=latitude, longitude=longitude) | ||
| + | |||
| + | weather = meteo.get_weather() | ||
| + | |||
| + | score_M = meteo.compute_weather_score(weather) | ||
| + | |||
| + | traffic = TrafficRepository() | ||
| + | |||
| + | data = traffic.get_traffic_by_gid(borneTraficGID) | ||
| + | |||
| + | score_T = traffic.compute_traffic_score(data) | ||
| + | |||
| + | tbm = TbmRepository(arret_id=4037) | ||
| + | |||
| + | prochain = tbm.get_next_tram() | ||
| + | |||
| + | if prochain and 'seconds' in prochain: | ||
| + | |||
| + | TP_seconds = prochain['seconds'] | ||
| + | |||
| + | else: | ||
| + | |||
| + | TP_seconds = 3600 | ||
| + | |||
| + | max_tp_seconds = 3600 | ||
| + | |||
| + | TP_score = max(0, min(100, 100 - (TP_seconds / max_tp_seconds) * 100)) | ||
| + | |||
| + | IMR = ( | ||
| + | |||
| + | 0.4 * (100 - aqi) + | ||
| + | |||
| + | 0.3 * (100 - score_T) + | ||
| + | |||
| + | 0.2 * TP_score + | ||
| + | |||
| + | 0.1 * score_M | ||
| + | |||
| + | ) | ||
| + | |||
| + | return {"IMR": round(IMR, 1)} | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | <br /> | ||
| + | |Step_Picture_00=Mobi_BOX_WhatsApp_Image_2026-05-30_at_13.46.41.jpeg | ||
| + | }} | ||
| + | {{Tuto Step | ||
| + | |Step_Title=Monter les éléments de la maquette et effectuer les tests | ||
| + | |Step_Picture_00=Mobi_BOX_IMG_0308.jpeg | ||
| + | |Step_Picture_01=Mobi_BOX_IMG_0345.mov | ||
| + | |Step_Picture_02=Mobi_BOX_IMG_0418.jpeg | ||
| + | |Step_Picture_03=Mobi_BOX_IMG_0384.mov | ||
| + | |Step_Picture_04=Mobi_BOX_IMG_1836.jpeg | ||
| + | }} | ||
| + | {{Tuto Step | ||
| + | |Step_Title=Vidéo final | ||
| + | |Step_Picture_00=Mobi_BOX_POCL.mp4 | ||
}} | }} | ||
| + | {{Notes | ||
| + | |Observations=Une rotation d’un cache dévoilé le moyen de transport conseillé à l’instant t. | ||
| + | |Avertissement=Connexion | ||
| + | |||
| + | Disponibilité des Api externe | ||
| + | |Explanations=L’esp appelle une api sur un serveur (api en fast api). Cette api va appeler les autres apis et faire les calcule. | ||
| + | |||
| + | Cette architecture permet de ne pas être limité par l’esp niveau performance. | ||
| + | |Applications=Ils sont installés dans les lieux publics, aux arrêts de tramway, aux stations de bus et dans les taxis, et sont signalés par un panneau indiquant leur présence. | ||
| + | |Related=- Ajouter un GPS afin d’avoir la position dynamiquement. | ||
| + | |||
| + | - Ajouter un capteur qui permet d’analyser la qualité de l’air pour ne plus dépendre d’un service tier et être plus précis | ||
| + | |Objectives=Améliorer notre capacité à travailler en équipe Améliorer notre capacité à s’adapter au différent profils et compétence des membres de notre équipe (oral, méthode de travail, contrainte) | ||
| + | |Notes=https://datahub.bordeaux-metropole.fr/pages/accueil/ | ||
| + | |||
| + | https://fastapi.tiangolo.com/ | ||
| + | |||
| + | https://open-meteo.com/ | ||
}} | }} | ||
| − | |||
| − | |||
{{Tuto Status | {{Tuto Status | ||
|Complete=Draft | |Complete=Draft | ||
}} | }} | ||
Auteur
AYOUB YACOUBI | Dernière modification 30/05/2026 par AYOUB
Pollution, transport, mobilité, qualité de l’air Mobi_BOX_IMG_0412.jpeg
En fonctionnement moteur, l'énergie électrique est transformée en énergie mécanique.En fonctionnement générateur, l'énergie mécanique est transformée en énergie électrique (elle peut se comporter comme un frein). Dans ce cas elle est aussi appelée dynamo.
Chaque participant partage sa compréhension du thème. Toutes les idées et les mots-clés sont ensuite notés et organisés sous forme de schéma.
Analyse des sources disponibles sur les sites de TBM et de Bordeaux Métropole pour identifier les bénéfices environnementaux du tramway et du réseau de bus.
Conception d’une machine capable d’analyser la qualité de l’air et le trafic, afin de proposer le meilleur moyen de transport entre bus, tram, voiture ou taxi.
Bois, LED, papier transparent, éléments imprimés en 3D, ainsi que des cartes électroniques(Arduino ) pour la rotation et l’éclairage des LED.
import requests
from bs4 import BeautifulSoup
class AQIRepository:
URL = "https://www.atmo-nouvelleaquitaine.org/air-commune/Bordeaux/33063/indice-atmo?date=2025-10-14"
def __init__(self):
self.cat_to_conc = {
'Bon': {'PM2.5': 5.0, 'PM10': 15.0, 'O3': 0.040, 'NO2': 20.0, 'SO2': 5.0},
'Moyen': {'PM2.5': 20.0, 'PM10': 40.0, 'O3': 0.060, 'NO2': 60.0, 'SO2': 20.0},
'Mauvais':{'PM2.5': 40.0, 'PM10': 100.0,'O3': 0.100, 'NO2': 150.0,'SO2': 100.0},
'Très mauvais':{'PM2.5':120.0,'PM10':300.0,'O3':0.180,'NO2':800.0,'SO2':500.0}
}
self.breakpoints = {
'PM2.5': [(0.0,12.0,0,50),(12.1,35.4,51,100),(35.5,55.4,101,150),(55.5,150.4,151,200)],
'PM10': [(0,54,0,50),(55,154,51,100),(155,254,101,150),(255,354,151,200)],
'O3': [(0.000,0.054,0,50),(0.055,0.070,51,100),(0.071,0.085,101,150)],
'NO2': [(0,53,0,50),(54,100,51,100),(101,360,101,150),(361,649,151,200)],
'SO2': [(0,35,0,50),(36,75,51,100),(76,185,101,150),(186,304,151,200)],
}
def linear_scale(self, C, C_low, C_high, I_low, I_high):
return round((I_high - I_low) / (C_high - C_low) * (C - C_low) + I_low)
def find_aqi_subindex(self, C, pollutant):
for (C_low, C_high, I_low, I_high) in self.breakpoints[pollutant]:
if C_low <= C <= C_high:
return self.linear_scale(C, C_low, C_high, I_low, I_high)
return None
def scrape_categories(self):
response = requests.get(self.URL, timeout=10)
soup = BeautifulSoup(response.text, "html.parser")
categories = {}
blocks = soup.select(".c-indice-polluant")
for block in blocks:
title_tag = block.select_one(".c-indice-polluant-title span")
cat_tag = block.select_one(".home-map-legend-item span")
if title_tag and cat_tag:
name = title_tag.get_text(strip=True).replace("₂", "2").replace("₃", "3")
cat = cat_tag.get_text(strip=True)
categories[name] = cat
return categories
def compute_aqi(self, categories):
concentrations = {k: self.cat_to_conc[v][k] for k, v in categories.items() if k in self.cat_to_conc[v]}
subindices = {k: self.find_aqi_subindex(concentrations[k], k) for k in concentrations}
aqi_global = max(v for v in subindices.values() if v is not None)
return aqi_global, subindices
import requests
class MeteoRepository:
def __init__(self, latitude=44.8378, longitude=-0.5792):
self.latitude = latitude
self.longitude = longitude
self.api_url = (
"https://api.open-meteo.com/v1/forecast?"
f"latitude={self.latitude}&longitude={self.longitude}"
"&hourly=temperature_2m,precipitation,weathercode"
"¤t_weather=true&timezone=Europe%2FParis"
)
def get_weather(self):
response = requests.get(self.api_url, timeout=10)
data = response.json()
return data.get("current_weather", {})
def compute_weather_score(self, weather):
wmo_scores = {
0: 100, 1: 90, 2: 80, 3: 70, 45: 60, 48: 55,
51: 50, 53: 45, 55: 40, 61: 35, 63: 25, 65: 15,
71: 25, 73: 15, 75: 5, 95: 10, 99: 0,
}
temp = weather.get("temperature", 20)
wind = weather.get("windspeed", 10)
code = weather.get("weathercode", 0)
base_score = wmo_scores.get(code, 60)
if temp < 0:
temp_penalty = 30
elif temp < 10:
temp_penalty = 15
elif temp > 30:
temp_penalty = 20
else:
temp_penalty = 0
wind_penalty = 0
if wind > 40:
wind_penalty = 20
elif wind > 25:
wind_penalty = 10
score = base_score - temp_penalty - wind_penalty
return round(max(0, min(100, score)))
import requests
import json
class TrafficRepository:
BASE_URL = "https://data.bordeaux-metropole.fr/geojson"
def __init__(self, api_key="2HH8S7FKPP", typename="ci_trafi_l"):
self.api_key = api_key
self.typename = typename
def get_traffic_by_gid(self, gid=2215):
params = {
"key": self.api_key,
"typename": self.typename,
"filter": json.dumps({"gid": gid}),
}
response = requests.get(self.BASE_URL, params=params, timeout=10)
if response.status_code != 200:
raise Exception(f"Erreur API {response.status_code} : {response.text}")
return response.json()
def compute_traffic_score(self, traffic_data):
features = traffic_data.get("features", [])
if not features:
return 50
props = features[0].get("properties", {})
etat = props.get("etat_trafic") or props.get("etat") or "INCONNU"
mapping = {"FLUIDE": 100, "RALENTI": 70, "EMBOUTEILLE": 30, "INCONNU": 50}
return mapping.get(etat.upper(), 50)
import requests
import json
from datetime import datetime, timezone
class TbmRepository:
BASE_URL = "https://data.bordeaux-metropole.fr/geojson"
def __init__(self, api_key="2HH8S7FKPP", arret_id=4037):
self.api_key = api_key
self.arret_id = arret_id
def get_next_tram(self, max_results=100):
params = {
"key": self.api_key,
"typename": "sv_horai_a",
"filter": json.dumps({"rs_sv_arret_p": self.arret_id}),
"orderby": json.dumps(["hor_theo"]),
"maxfeatures": max_results
}
response = requests.get(self.BASE_URL, params=params)
response.raise_for_status()
data = response.json()
now = datetime.now(timezone.utc)
for feature in data.get("features", []):
props = feature.get("properties", {})
hor_theo = props.get("hor_theo")
if not hor_theo:
continue
try:
tram_time = datetime.fromisoformat(hor_theo)
except ValueError:
continue
if tram_time > now:
diff_sec = int((tram_time - now).total_seconds())
return {"heure": tram_time, "seconds": diff_sec, "etat": props.get("etat"), "type": props.get("type")}
return None
from AQIRepository import AQIRepository
from MeteoRepository import MeteoRepository
from TrafficRepository import TrafficRepository
from TramRepository import TbmRepository
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
longitude = -0.5935101
latitude = 44.8538665
borneTraficGID = 2215
Aqirepo = AQIRepository()
categories = Aqirepo.scrape_categories()
aqi, details = Aqirepo.compute_aqi(categories)
meteo = MeteoRepository(latitude=latitude, longitude=longitude)
weather = meteo.get_weather()
score_M = meteo.compute_weather_score(weather)
traffic = TrafficRepository()
data = traffic.get_traffic_by_gid(borneTraficGID)
score_T = traffic.compute_traffic_score(data)
tbm = TbmRepository(arret_id=4037)
prochain = tbm.get_next_tram()
if prochain and 'seconds' in prochain:
TP_seconds = prochain['seconds']
else:
TP_seconds = 3600
max_tp_seconds = 3600
TP_score = max(0, min(100, 100 - (TP_seconds / max_tp_seconds) * 100))
IMR = (
0.4 * (100 - aqi) +
0.3 * (100 - score_T) +
0.2 * TP_score +
0.1 * score_M
)
return {"IMR": round(IMR, 1)}
Une rotation d’un cache dévoilé le moyen de transport conseillé à l’instant t.
Connexion
Disponibilité des Api externe
L’esp appelle une api sur un serveur (api en fast api). Cette api va appeler les autres apis et faire les calcule.
Cette architecture permet de ne pas être limité par l’esp niveau performance.
Ils sont installés dans les lieux publics, aux arrêts de tramway, aux stations de bus et dans les taxis, et sont signalés par un panneau indiquant leur présence.
- Ajouter un GPS afin d’avoir la position dynamiquement.
- Ajouter un capteur qui permet d’analyser la qualité de l’air pour ne plus dépendre d’un service tier et être plus précis
Améliorer notre capacité à travailler en équipe Améliorer notre capacité à s’adapter au différent profils et compétence des membres de notre équipe (oral, méthode de travail, contrainte)
https://datahub.bordeaux-metropole.fr/pages/accueil/
Dernière modification 30/05/2026 par user:AYOUB.
Draft
Vous avez entré un nom de page invalide, avec un ou plusieurs caractères suivants :
< > @ ~ : * € £ ` + = / \ | [ ] { } ; ? #