first commit

This commit is contained in:
mathieu 2025-07-14 17:56:57 +02:00
commit a233e18c0b
48 changed files with 55300 additions and 0 deletions

3
demos/__init__.py Normal file
View file

@ -0,0 +1,3 @@
"""
Demos package for Atmo Data Wrapper
"""

View file

@ -0,0 +1,343 @@
#!/usr/bin/env python3
"""
Script de démonstration de toutes les fonctions du datamodel IndiceAtmo
Utilise les données réelles de l'API pour une ville française (aujourd'hui)
Idéal pour documentation et exemples
"""
from atmo_data_wrapper import AtmoDataClient, AtmoDataException
from atmo_data_wrapper import CODE_POLLUANT, INDICES_ATMO
from datetime import datetime
import sys
def demo_atmo_functions():
"""Démonstration complète des fonctions IndiceAtmo avec données réelles"""
# Configuration
AASQA_ILE_DE_FRANCE = "11" # Île-de-France pour avoir des données
today = datetime.now().strftime('%Y-%m-%d')
print("🌬️ DÉMONSTRATION DES FONCTIONS INDICEATMO")
print("=" * 55)
print(f"📍 Région: Île-de-France (AASQA: {AASQA_ILE_DE_FRANCE})")
print(f"🗓️ Date: {today}")
print(f"🏛️ Basé sur la notice officielle du 1er avril 2025")
print()
try:
# Connexion et récupération des données
print("🔐 Connexion à l'API...")
client = AtmoDataClient()
success = client.auto_login()
if not success:
print("❌ Échec de la connexion à l'API")
return False
print("✅ Connecté avec succès !")
# Récupération des données ATMO
print(f"🌬️ Récupération des indices ATMO pour {today}...")
indices = client.get_indices_atmo(
format="geojson",
date=today,
aasqa=AASQA_ILE_DE_FRANCE
)
if not indices or len(indices) == 0:
print("❌ Aucune donnée ATMO trouvée")
return False
print(f"{len(indices)} station(s) trouvée(s)")
# Utiliser la première station pour la démo
atmo = indices[0]
print(f"📍 Station sélectionnée: {atmo.lib_zone}")
print()
# === DÉMONSTRATION DES PROPRIÉTÉS DE BASE ===
print("📊 === PROPRIÉTÉS DE BASE (héritées d'AtmoDataBase) ===")
print()
print("🏛️ INFORMATIONS GÉNÉRALES:")
print(f" • AASQA: {atmo.aasqa}")
print(f" • Zone: {atmo.lib_zone}")
print(f" • Source: {atmo.source}")
print(f" • Type de zone: {atmo.type_zone}")
print(f" • Code zone: {atmo.code_zone}")
print()
print("📅 DONNÉES TEMPORELLES:")
print(f" • Date échéance: {atmo.date_ech}")
print(f" • Date diffusion: {atmo.date_dif}")
print()
print("🗺️ COORDONNÉES:")
if atmo.has_coordinates():
print(f" • Coordonnées disponibles: Oui")
if hasattr(atmo.coordinates, 'latitude'):
print(f" • Latitude: {atmo.coordinates.latitude:.6f}")
print(f" • Longitude: {atmo.coordinates.longitude:.6f}")
else:
print(" • Pas de coordonnées WGS84 disponibles")
print(f" • Coordonnées réglementaires: ({atmo.x_reg:.1f}, {atmo.y_reg:.1f})")
print(f" • Système de projection: EPSG:{atmo.epsg_reg}")
print()
print("🎨 FONCTIONS CENTRALISÉES:")
for level in [0, 1, 2, 3, 4, 5, 6, 7]:
emoji_round = atmo.get_emoji_by_level(level, "round")
emoji_square = atmo.get_emoji_by_level(level, "square")
color_hex, color_rgb = atmo.get_color_by_level(level)
qualif = INDICES_ATMO.get(level, "Inconnu")
print(f" • Niveau {level} ({qualif}): {emoji_round}{emoji_square} {color_hex} {color_rgb}")
print()
# === DÉMONSTRATION DES PROPRIÉTÉS SPÉCIFIQUES ATMO ===
print("🌬️ === PROPRIÉTÉS SPÉCIFIQUES ATMO ===")
print()
print("📈 INDICE GLOBAL:")
print(f" • Code qualificatif: {atmo.code_qual}")
print(f" • Libellé qualificatif: {atmo.lib_qual}")
print(f" • Couleur qualificatif: {atmo.coul_qual}")
print()
print("📊 CODES PAR POLLUANT:")
print(f" • Dioxyde d'azote (NO2): {atmo.code_no2}")
print(f" • Dioxyde de soufre (SO2): {atmo.code_so2}")
print(f" • Ozone (O3): {atmo.code_o3}")
print(f" • Particules PM10: {atmo.code_pm10}")
print(f" • Particules PM2.5: {atmo.code_pm25}")
print()
print("🔬 CONCENTRATIONS (μg/m³) - FACULTATIVES:")
print(f" • NO2: {atmo.conc_no2}")
print(f" • SO2: {atmo.conc_so2}")
print(f" • O3: {atmo.conc_o3}")
print(f" • PM10: {atmo.conc_pm10}")
print(f" • PM2.5: {atmo.conc_pm25}")
print()
# === DÉMONSTRATION DES MÉTHODES HELPER ===
print("🛠️ === MÉTHODES HELPER ===")
print()
# 1. get_qualificatif()
print("📋 1. QUALIFICATIF TEXTUEL:")
qualificatif = atmo.get_qualificatif()
print(f" • get_qualificatif(): '{qualificatif}'")
print(f" → Qualificatif de l'indice global")
print()
# 2. get_color()
print("🎨 2. COULEUR ASSOCIÉE:")
color_hex, color_rgb = atmo.get_color()
print(f" • get_color(): ('{color_hex}', {color_rgb})")
print(f" → Couleur hexadécimale et RGB de l'indice")
print()
# 3. get_emoji()
print("😀 3. ÉMOJIS:")
emoji_round = atmo.get_emoji("round")
emoji_square = atmo.get_emoji("square")
print(f" • get_emoji('round'): '{emoji_round}'")
print(f" • get_emoji('square'): '{emoji_square}'")
print(f" → Émojis rond et carré selon le niveau")
print()
# 4. Tests de qualité
print("✅ 4. TESTS DE QUALITÉ:")
is_good = atmo.is_good_quality()
is_poor = atmo.is_poor_quality()
print(f" • is_good_quality(): {is_good}")
print(f" → Qualité bonne (niveaux 1-2): {'Oui' if is_good else 'Non'}")
print(f" • is_poor_quality(): {is_poor}")
print(f" → Qualité dégradée (niveaux 4+): {'Oui' if is_poor else 'Non'}")
print()
# 5. get_worst_pollutant()
print("🔍 5. POLLUANT LE PLUS PROBLÉMATIQUE:")
worst_pollutant, worst_code = atmo.get_worst_pollutant()
worst_description = CODE_POLLUANT.get(worst_pollutant, worst_pollutant)
print(f" • get_worst_pollutant(): ('{worst_pollutant}', {worst_code})")
print(f" → Polluant: {worst_description}")
print(f" → Niveau: {worst_code} ({INDICES_ATMO.get(worst_code, 'Inconnu')})")
print()
# 6. get_pollutants_summary()
print("📋 6. RÉSUMÉ COMPLET DES POLLUANTS:")
summary = atmo.get_pollutants_summary()
print(f" • get_pollutants_summary():")
print(" → Structure complète par polluant:")
for polluant, info in summary.items():
if info['code'] > 0: # Afficher seulement les polluants avec des données
print(f" - {polluant} ({info['description']}):")
print(f" * Code: {info['code']}")
print(f" * Qualificatif: {info['qualificatif']}")
print()
# 7. get_concentrations() - NOUVELLE MÉTHODE
print("🔬 7. CONCENTRATIONS SELON NOTICE OFFICIELLE:")
concentrations = atmo.get_concentrations()
print(f" • get_concentrations(): {concentrations}")
print(" → Concentrations en μg/m³:")
for polluant, conc in concentrations.items():
description = CODE_POLLUANT.get(polluant, polluant)
print(f" - {description}: {conc} μg/m³")
print()
# 8. Nouvelles méthodes conformité notice
print("📋 8. MÉTHODES CONFORMITÉ NOTICE OFFICIELLE:")
is_commune = atmo.is_commune_level()
is_epci = atmo.is_epci_level()
responsible = atmo.get_responsible_pollutants()
print(f" • is_commune_level(): {is_commune}")
print(f" → Calculé au niveau commune: {'Oui' if is_commune else 'Non'}")
print(f" • is_epci_level(): {is_epci}")
print(f" → Calculé au niveau EPCI: {'Oui' if is_epci else 'Non'}")
print(f" • get_responsible_pollutants(): {responsible}")
if responsible:
resp_descriptions = [CODE_POLLUANT.get(p, p) for p in responsible]
print(f" → Polluants responsables: {', '.join(resp_descriptions)}")
print()
# === DÉMONSTRATION DES MÉTHODES STRING ===
print("📝 === REPRÉSENTATION STRING ===")
print()
print("🔤 MÉTHODE __str__():")
print(f" • str(atmo): '{str(atmo)}'")
print()
# === EXEMPLES D'UTILISATION PRATIQUE ===
print("💡 === EXEMPLES D'UTILISATION PRATIQUE ===")
print()
print("🎯 ANALYSE RAPIDE:")
print(f" • Qualité globale: {qualificatif} (niveau {atmo.code_qual})")
print(f" • Polluant problématique: {worst_description} (niveau {worst_code})")
print(f" • Couleur d'affichage: {color_hex} {emoji_round}")
if is_poor:
print(" • ⚠️ QUALITÉ DÉGRADÉE - Précautions recommandées")
elif is_good:
print(" • ✅ BONNE QUALITÉ DE L'AIR")
else:
print(" • 🔶 QUALITÉ MOYENNE")
print()
print("📈 ANALYSE PAR POLLUANT:")
# Analyse des polluants selon leurs niveaux
pollutants_analysis = {
'NO2': (atmo.code_no2, "Dioxyde d'azote"),
'SO2': (atmo.code_so2, "Dioxyde de soufre"),
'O3': (atmo.code_o3, "Ozone"),
'PM10': (atmo.code_pm10, "Particules PM10"),
'PM2.5': (atmo.code_pm25, "Particules PM2.5")
}
for code, (level, description) in pollutants_analysis.items():
if level > 0:
emoji = atmo.get_emoji_by_level(level)
qualif = INDICES_ATMO.get(level, "Inconnu")
print(f"{description}: {emoji} {qualif} (niveau {level})")
print()
print("🔍 FILTRAGE ET CLASSIFICATION:")
# Classification selon les seuils
critical_pollutants = [p for p, (l, _) in pollutants_analysis.items() if l >= 4]
moderate_pollutants = [p for p, (l, _) in pollutants_analysis.items() if 2 <= l <= 3]
good_pollutants = [p for p, (l, _) in pollutants_analysis.items() if l == 1]
if critical_pollutants:
print(f" • Polluants critiques (≥4): {', '.join(critical_pollutants)}")
if moderate_pollutants:
print(f" • Polluants modérés (2-3): {', '.join(moderate_pollutants)}")
if good_pollutants:
print(f" • Polluants bons (1): {', '.join(good_pollutants)}")
if not critical_pollutants and not moderate_pollutants:
print(" • ✅ Tous les polluants à des niveaux acceptables")
print()
# === INFORMATIONS TECHNIQUES ===
print("🔧 === INFORMATIONS TECHNIQUES ===")
print()
print("📦 STRUCTURE DE DONNÉES:")
print(f" • Type d'objet: {type(atmo).__name__}")
print(f" • Classe parente: {type(atmo).__bases__[0].__name__}")
print(f" • Propriétés disponibles: {len(atmo.properties)} champs")
print(f" • Géométrie: {'Oui' if atmo.has_coordinates() else 'Non'}")
print(f" • Zone géographique: {atmo.type_zone}")
print()
print("🎨 MÉTHODES HÉRITÉES:")
inherited_methods = [
'get_emoji_by_level()', 'get_color_by_level()', 'has_coordinates()',
'get_source()', 'get_aasqa_name()'
]
print(f" • Méthodes de AtmoDataBase: {', '.join(inherited_methods)}")
print()
specific_methods = [
'get_qualificatif()', 'get_color()', 'get_emoji()', 'is_good_quality()',
'is_poor_quality()', 'get_worst_pollutant()', 'get_pollutants_summary()',
'get_concentrations()', 'is_commune_level()', 'is_epci_level()', 'get_responsible_pollutants()'
]
print(f" • Méthodes spécifiques IndiceAtmo: {', '.join(specific_methods)}")
print()
print("🎨 NOUVEAUTÉS ÉMOJIS:")
print(" • get_emoji_by_level(level, style) - style='round'|'square'")
print(" • get_emoji(style) - avec choix de style d'émoji")
print(" • Support des émojis ronds (🟢) et carrés (🟩)")
print()
print("📋 CONFORMITÉ NOTICE OFFICIELLE (1er avril 2025):")
print(" • Tous les champs obligatoires et facultatifs supportés")
print(" • Propriétés conformes aux spécifications pages 12-14")
print(" • Méthodes basées sur les règles officielles de calcul")
print(" • Codes couleur conformes au tableau page 6")
print(" • Support des coordonnées réglementaires (Lambert 93)")
print(" • Concentrations facultatives selon format officiel")
print()
print("✅ === DÉMONSTRATION TERMINÉE ===")
print()
print("📚 Ce script illustre toutes les fonctionnalités de la classe IndiceAtmo")
print("🔧 Utilisez ces exemples pour votre documentation et vos développements")
print("📋 Conforme à l'arrêté du 10 juillet 2020 et à la notice du 1er avril 2025")
print()
return True
except AtmoDataException as e:
print(f"❌ Erreur API: {e}")
return False
except Exception as e:
print(f"❌ Erreur inattendue: {e}")
import traceback
traceback.print_exc()
return False
def main():
"""Point d'entrée principal"""
print("🌬️ Démonstration des fonctions IndiceAtmo")
print("=" * 55)
print()
success = demo_atmo_functions()
if not success:
print("\n❌ La démonstration s'est terminée avec des erreurs")
sys.exit(1)
else:
print("🎉 Démonstration terminée avec succès !")
sys.exit(0)
if __name__ == "__main__":
main()

165
demos/demo_complete.py Normal file
View file

@ -0,0 +1,165 @@
#!/usr/bin/env python3
"""
Démonstration complète du wrapper Atmo Data API
avec connexion réelle et toutes les fonctionnalités
"""
from atmo_data_wrapper import AtmoDataClient, AtmoDataException
from datetime import datetime, timedelta
import os
def main():
"""Démonstration complète de toutes les fonctionnalités"""
print("🌍 === DÉMONSTRATION ATMO DATA API ===\n")
try:
# 1. Connexion automatique
print("1. 🔐 Connexion à l'API...")
client = AtmoDataClient()
success = client.auto_login()
if not success:
print("❌ Échec de connexion")
return
print(f"✅ Connecté à {client.base_url}")
print(f" Token: {client.token[:20]}...")
print()
# 2. Indices ATMO avec objets typés
print("2. 🏭 Récupération des indices de qualité de l'air...")
today = datetime.now().strftime('%Y-%m-%d')
indices = client.get_indices_atmo(date=today, aasqa='11') # Île-de-France
print(f"{len(indices)} indices récupérés")
print(f" Résumé: {indices.to_summary()}")
# Examiner quelques indices
print("\n 📊 Exemples d'indices:")
for i, indice in enumerate(indices[:3]):
print(f"{indice.lib_zone}: {indice.get_qualificatif()}")
if indice.is_poor_quality():
worst_pol, code = indice.get_worst_pollutant()
print(f" ⚠️ Attention: {worst_pol} élevé ({code})")
print()
# 3. Épisodes de pollution
print("3. 🚨 Vérification des épisodes de pollution...")
episodes = client.get_episodes_3jours(aasqa='11')
print(f"{len(episodes)} épisodes trouvés")
alerts_actives = [ep for ep in episodes if ep.is_alert_active()]
if alerts_actives:
print(f" ⚠️ {len(alerts_actives)} alertes actives:")
for ep in alerts_actives[:3]:
print(f"{ep.lib_zone}: {ep.get_alert_level()} - {ep.lib_pol}")
else:
print(" ✅ Aucune alerte active")
print()
# 4. Données d'émissions
print("4. 🏭 Analyse des émissions...")
emissions = client.get_emissions(aasqa='11', echelle='region')
print(f"{len(emissions)} territoires analysés")
if len(emissions) > 0:
em = emissions[0]
print(f" 📍 {em.name}:")
print(f" • Population: {em.population:,.0f} habitants")
total_em = em.get_total_emissions()
print(f" • Émissions NOx: {total_em['NOx']:,.1f} t/an")
print(f" • Émissions PM10: {total_em['PM10']:,.1f} t/an")
# Calculs par habitant
nox_per_capita = em.get_emission_per_capita('nox') * 1000 # kg/hab
print(f" • NOx par habitant: {nox_per_capita:.2f} kg/hab/an")
print()
# 5. Indices pollen
print("5. 🌸 Vérification des indices pollen...")
try:
pollens = client.get_indices_pollens(aasqa='11')
print(f"{len(pollens)} stations pollen")
alerts_pollen = [p for p in pollens if p.is_alert_active()]
if alerts_pollen:
print(f" ⚠️ {len(alerts_pollen)} alertes pollen actives")
for pol in alerts_pollen[:3]:
dangerous = pol.get_dangerous_pollens()
if dangerous:
print(f" • Pollens à risque: {', '.join(dangerous)}")
else:
print(" ✅ Pas d'alerte pollen majeure")
except Exception as e:
print(f" ⚠️ Indices pollen indisponibles: {e}")
print()
# 6. Sauvegarde des données
print("6. 💾 Sauvegarde des données...")
try:
# Créer le dossier de sauvegarde
save_dir = f"export_{today}"
# Sauvegarder en différents formats
json_file = client.save_to_file(indices.raw_data, f"{save_dir}/indices_idf", "json")
csv_file = client.save_to_file(indices.raw_data, f"{save_dir}/indices_idf", "csv")
print(f"✅ Données sauvegardées:")
print(f" • JSON: {json_file} ({os.path.getsize(json_file):,} bytes)")
print(f" • CSV: {csv_file} ({os.path.getsize(csv_file):,} bytes)")
except Exception as e:
print(f" ❌ Erreur sauvegarde: {e}")
print()
# 7. Analyse statistique
print("7. 📈 Analyse statistique...")
stats = indices.get_statistics()
qs = stats['quality_stats']
print(f"✅ Statistiques de qualité de l'air:")
print(f" • Qualité moyenne: {qs['moyenne']:.1f}/7")
print(f" • Bonne qualité: {qs['bon_pourcentage']:.1f}% des zones")
print(f" • Meilleur indice: {qs['min']}")
print(f" • Pire indice: {qs['max']}")
# Classification des zones
bonnes = [i for i in indices if i.is_good_quality()]
mauvaises = [i for i in indices if i.is_poor_quality()]
print(f" • Zones de bonne qualité: {len(bonnes)}")
print(f" • Zones de qualité dégradée: {len(mauvaises)}")
if mauvaises:
print(" ⚠️ Zones à surveiller:")
for zone in mauvaises[:5]:
print(f" - {zone.lib_zone}: {zone.get_qualificatif()}")
print()
# 8. Recommandations
print("8. 💡 Recommandations...")
if qs['moyenne'] <= 2:
print("✅ Qualité de l'air globalement bonne en Île-de-France")
elif qs['moyenne'] <= 3:
print("⚠️ Qualité de l'air modérée - Surveillance recommandée")
else:
print("🚨 Qualité de l'air dégradée - Précautions recommandées")
if alerts_actives:
print("🚨 Épisodes de pollution en cours - Limitez les activités extérieures")
print("\n🎉 === DÉMONSTRATION TERMINÉE ===")
print(f"📊 Données analysées: {len(indices)} indices, {len(episodes)} épisodes, {len(emissions)} territoires")
print("💾 Fichiers exportés dans le dossier export_*")
except AtmoDataException as e:
print(f"❌ Erreur API: {e}")
except Exception as e:
print(f"❌ Erreur inattendue: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,472 @@
#!/usr/bin/env python3
"""
Script de démonstration des fonctionnalités de la classe EmissionData
================================================================
Ce script illustre l'utilisation de toutes les méthodes disponibles
dans la classe EmissionData pour analyser les données d'émissions.
Fonctionnalités testées:
- Analyse des émissions par polluant
- Calcul de densités d'émission par km²
- Calcul d'émissions par habitant
- Analyse des secteurs d'émission
- Gestion des coordonnées géographiques
- Utilisation des méthodes héritées de la classe de base
"""
import json
from pathlib import Path
from atmo_wrapper import AtmoDataWrapper
from atmo_data_wrapper import EmissionData, AtmoDataCollection, Coordinates
from atmo_data_wrapper import SECTEURS_EMISSIONS
def demo_basic_properties():
"""Démonstration des propriétés de base d'un objet EmissionData"""
print("=" * 60)
print("DÉMONSTRATION DES PROPRIÉTÉS DE BASE")
print("=" * 60)
# Exemple de données d'émission simulées
sample_emission = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [6.1667, 49.1333] # Nancy
},
"properties": {
"aasqa": "90",
"source": "ATMO Grand Est",
"date_maj": "2024-01-15",
"lib_zone": "Nancy Métropole",
"code": "54395",
"name": "Nancy",
"population": 104885,
"superficie": 15.01,
"nox": 125.5,
"pm10": 45.2,
"pm25": 28.7,
"ges": 850.3,
"code_pcaet": "01"
}
}
emission = EmissionData(sample_emission)
print(f"Code zone: {emission.code}")
print(f"Nom: {emission.name}")
print(f"Population: {emission.population:,} habitants")
print(f"Superficie: {emission.superficie} km²")
print(f"AASQA: {emission.get_aasqa_name()}")
print(f"Source: {emission.get_source()}")
print(f"Date de mise à jour: {emission.date_maj}")
print(f"Zone: {emission.lib_zone}")
print(f"Secteur: {emission.get_secteur_name()}")
if emission.has_coordinates():
print(f"Coordonnées: {emission.coordinates}")
else:
print("Pas de coordonnées disponibles")
print(f"Représentation: {emission}")
print()
def demo_emission_analysis():
"""Démonstration de l'analyse des émissions"""
print("=" * 60)
print("ANALYSE DES ÉMISSIONS PAR POLLUANT")
print("=" * 60)
# Exemple avec plusieurs zones d'émission
emission_samples = [
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [6.1667, 49.1333]},
"properties": {
"aasqa": "90", "name": "Nancy", "population": 104885, "superficie": 15.01,
"nox": 125.5, "pm10": 45.2, "pm25": 28.7, "ges": 850.3, "code_pcaet": "01"
}
},
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [7.7500, 48.5833]},
"properties": {
"aasqa": "90", "name": "Strasbourg", "population": 280966, "superficie": 78.26,
"nox": 285.7, "pm10": 92.1, "pm25": 58.4, "ges": 1850.9, "code_pcaet": "02"
}
},
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [6.2000, 49.2500]},
"properties": {
"aasqa": "90", "name": "Metz", "population": 116429, "superficie": 41.94,
"nox": 155.8, "pm10": 52.3, "pm25": 35.1, "ges": 950.7, "code_pcaet": "03"
}
}
]
for i, emission_data in enumerate(emission_samples, 1):
emission = EmissionData(emission_data)
print(f"{i}. {emission.name}")
print(f" Population: {emission.population:,} hab, Superficie: {emission.superficie} km²")
# Émissions totales
total_emissions = emission.get_total_emissions()
print(f" Émissions totales:")
for polluant, valeur in total_emissions.items():
print(f" - {polluant}: {valeur:.1f} tonnes/an")
print()
def demo_density_calculations():
"""Démonstration des calculs de densité d'émission"""
print("=" * 60)
print("CALCULS DE DENSITÉ D'ÉMISSION (tonnes/km²)")
print("=" * 60)
emission_data = {
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [2.3522, 48.8566]},
"properties": {
"aasqa": "18", "name": "Paris", "population": 2161000, "superficie": 105.4,
"nox": 1250.5, "pm10": 425.2, "pm25": 280.7, "ges": 8500.3, "code_pcaet": "01"
}
}
emission = EmissionData(emission_data)
print(f"Zone: {emission.name}")
print(f"Superficie: {emission.superficie} km²")
print()
print("Densités d'émission par km²:")
pollutants = ['nox', 'pm10', 'pm25', 'ges']
for pollutant in pollutants:
density = emission.get_emission_density(pollutant)
polluant_name = pollutant.upper().replace('GES', 'GES (CO2 eq)')
print(f" - {polluant_name}: {density:.2f} tonnes/km²")
print()
def demo_per_capita_calculations():
"""Démonstration des calculs d'émission par habitant"""
print("=" * 60)
print("CALCULS D'ÉMISSION PAR HABITANT (tonnes/hab/an)")
print("=" * 60)
emission_data = {
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [3.0573, 50.6292]},
"properties": {
"aasqa": "59", "name": "Lille Métropole", "population": 1182127, "superficie": 611.0,
"nox": 1580.3, "pm10": 520.7, "pm25": 315.8, "ges": 11250.5, "code_pcaet": "02"
}
}
emission = EmissionData(emission_data)
print(f"Zone: {emission.name}")
print(f"Population: {emission.population:,} habitants")
print()
print("Émissions par habitant (en tonnes/habitant/an):")
pollutants = ['nox', 'pm10', 'pm25', 'ges']
for pollutant in pollutants:
per_capita = emission.get_emission_per_capita(pollutant)
polluant_name = pollutant.upper().replace('GES', 'GES (CO2 eq)')
# Convertir en kg/hab/an pour plus de lisibilité
per_capita_kg = per_capita * 1000
print(f" - {polluant_name}: {per_capita:.6f} t/hab/an ({per_capita_kg:.2f} kg/hab/an)")
print()
def demo_secteur_analysis():
"""Démonstration de l'analyse par secteur d'émission"""
print("=" * 60)
print("ANALYSE PAR SECTEUR D'ÉMISSION")
print("=" * 60)
# Simulation de différents secteurs d'émission
secteurs_samples = [
{"code_pcaet": "01", "name": "Transport routier", "nox": 850.5, "pm10": 45.2},
{"code_pcaet": "02", "name": "Industrie manufacturière", "nox": 320.8, "pm10": 85.7},
{"code_pcaet": "03", "name": "Résidentiel", "nox": 125.3, "pm10": 95.4},
{"code_pcaet": "04", "name": "Agriculture", "nox": 85.2, "pm10": 125.8},
{"code_pcaet": "05", "name": "Tertiaire", "nox": 65.7, "pm10": 25.3}
]
print("Émissions par secteur:")
print()
for i, secteur_data in enumerate(secteurs_samples, 1):
emission_data = {
"type": "Feature",
"properties": {
"aasqa": "25", "name": f"Zone {i}", "population": 50000, "superficie": 25.0,
"nox": secteur_data["nox"], "pm10": secteur_data["pm10"], "pm25": 15.0, "ges": 500.0,
"code_pcaet": secteur_data["code_pcaet"]
}
}
emission = EmissionData(emission_data)
print(f"{i}. Secteur: {emission.get_secteur_name()}")
print(f" Code PCAET: {emission.code_pcaet}")
print(f" Émissions NOx: {emission.nox:.1f} t/an")
print(f" Émissions PM10: {emission.pm10:.1f} t/an")
print()
def demo_coordinate_functions():
"""Démonstration des fonctions de coordonnées géographiques"""
print("=" * 60)
print("FONCTIONS DE COORDONNÉES GÉOGRAPHIQUES")
print("=" * 60)
# Créer des émissions avec coordonnées
emission_lille = EmissionData({
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [3.0573, 50.6292]},
"properties": {
"aasqa": "59", "name": "Lille", "population": 233897, "superficie": 34.8,
"nox": 285.5, "pm10": 85.2, "pm25": 55.7, "ges": 1250.3, "code_pcaet": "01"
}
})
emission_nancy = EmissionData({
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [6.1667, 49.1333]},
"properties": {
"aasqa": "90", "name": "Nancy", "population": 104885, "superficie": 15.01,
"nox": 125.5, "pm10": 45.2, "pm25": 28.7, "ges": 850.3, "code_pcaet": "01"
}
})
print(f"Émission 1: {emission_lille.name}")
print(f" Coordonnées: {emission_lille.coordinates}")
print(f" A des coordonnées: {emission_lille.has_coordinates()}")
print()
print(f"Émission 2: {emission_nancy.name}")
print(f" Coordonnées: {emission_nancy.coordinates}")
print(f" A des coordonnées: {emission_nancy.has_coordinates()}")
print()
# Calcul de distance
if emission_lille.has_coordinates() and emission_nancy.has_coordinates():
distance = emission_lille.coordinates.distance_to(emission_nancy.coordinates)
print(f"Distance entre {emission_lille.name} et {emission_nancy.name}: {distance:.1f} km")
print()
def demo_inherited_methods():
"""Démonstration des méthodes héritées de la classe de base"""
print("=" * 60)
print("MÉTHODES HÉRITÉES DE LA CLASSE DE BASE")
print("=" * 60)
emission_data = {
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [4.8357, 45.7640]},
"properties": {
"aasqa": "84", "name": "Lyon Métropole", "population": 1398892, "superficie": 533.68,
"nox": 1850.7, "pm10": 625.4, "pm25": 385.2, "ges": 13250.8, "code_pcaet": "02",
"source": "ATMO Auvergne-Rhône-Alpes", "date_maj": "2024-01-15"
}
}
emission = EmissionData(emission_data)
print(f"Zone: {emission.name}")
print(f"AASQA: {emission.get_aasqa_name()}")
print(f"Source: {emission.get_source()}")
print()
# Test des fonctions de couleur et emoji (niveau fictif pour démonstration)
print("Fonctions de couleur et emoji (exemple avec niveau 3):")
test_level = 3
couleur_hex, couleur_rgb = emission.get_color_by_level(test_level)
emoji_round = emission.get_emoji_by_level(test_level, "round")
emoji_square = emission.get_emoji_by_level(test_level, "square")
print(f" - Couleur hex: {couleur_hex}")
print(f" - Couleur RGB: {couleur_rgb}")
print(f" - Emoji rond: {emoji_round}")
print(f" - Emoji carré: {emoji_square}")
print()
def demo_comparative_analysis():
"""Démonstration d'une analyse comparative entre plusieurs zones"""
print("=" * 60)
print("ANALYSE COMPARATIVE ENTRE ZONES")
print("=" * 60)
zones_data = [
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [2.3522, 48.8566]},
"properties": {
"aasqa": "18", "name": "Paris", "population": 2161000, "superficie": 105.4,
"nox": 1250.5, "pm10": 425.2, "pm25": 280.7, "ges": 8500.3, "code_pcaet": "01"
}
},
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [4.8357, 45.7640]},
"properties": {
"aasqa": "84", "name": "Lyon", "population": 1398892, "superficie": 533.68,
"nox": 1850.7, "pm10": 625.4, "pm25": 385.2, "ges": 13250.8, "code_pcaet": "02"
}
},
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [5.3698, 43.2965]},
"properties": {
"aasqa": "13", "name": "Marseille", "population": 868277, "superficie": 240.62,
"nox": 980.3, "pm10": 385.7, "pm25": 245.8, "ges": 7250.5, "code_pcaet": "01"
}
}
]
emissions = [EmissionData(zone) for zone in zones_data]
print("Comparaison des émissions par habitant (kg/hab/an):")
print("-" * 55)
print(f"{'Zone':<15} {'NOx':<8} {'PM10':<8} {'PM2.5':<8} {'GES':<10}")
print("-" * 55)
for emission in emissions:
nox_per_cap = emission.get_emission_per_capita('nox') * 1000 # Conversion en kg
pm10_per_cap = emission.get_emission_per_capita('pm10') * 1000
pm25_per_cap = emission.get_emission_per_capita('pm25') * 1000
ges_per_cap = emission.get_emission_per_capita('ges') * 1000
print(f"{emission.name:<15} {nox_per_cap:<8.1f} {pm10_per_cap:<8.1f} {pm25_per_cap:<8.1f} {ges_per_cap:<10.1f}")
print()
print("Comparaison des densités d'émission (tonnes/km²):")
print("-" * 55)
print(f"{'Zone':<15} {'NOx':<8} {'PM10':<8} {'PM2.5':<8} {'GES':<10}")
print("-" * 55)
for emission in emissions:
nox_density = emission.get_emission_density('nox')
pm10_density = emission.get_emission_density('pm10')
pm25_density = emission.get_emission_density('pm25')
ges_density = emission.get_emission_density('ges')
print(f"{emission.name:<15} {nox_density:<8.1f} {pm10_density:<8.1f} {pm25_density:<8.1f} {ges_density:<10.1f}")
print()
def demo_edge_cases():
"""Démonstration de la gestion des cas particuliers"""
print("=" * 60)
print("GESTION DES CAS PARTICULIERS")
print("=" * 60)
# Cas 1: Données manquantes
print("1. Zone sans coordonnées:")
emission_no_coords = EmissionData({
"type": "Feature",
"properties": {
"aasqa": "99", "name": "Zone sans coordonnées", "population": 10000, "superficie": 20.0,
"nox": 50.0, "pm10": 20.0, "pm25": 15.0, "ges": 300.0, "code_pcaet": "01"
}
})
print(f" A des coordonnées: {emission_no_coords.has_coordinates()}")
print(f" Coordonnées: {emission_no_coords.coordinates}")
print()
# Cas 2: Population nulle (division par zéro)
print("2. Zone sans population:")
emission_no_pop = EmissionData({
"type": "Feature",
"properties": {
"aasqa": "99", "name": "Zone industrielle", "population": 0, "superficie": 5.0,
"nox": 150.0, "pm10": 25.0, "pm25": 18.0, "ges": 800.0, "code_pcaet": "02"
}
})
nox_per_cap = emission_no_pop.get_emission_per_capita('nox')
print(f" Émission NOx par habitant: {nox_per_cap} (population = 0)")
print()
# Cas 3: Superficie nulle
print("3. Zone sans superficie:")
emission_no_area = EmissionData({
"type": "Feature",
"properties": {
"aasqa": "99", "name": "Point source", "population": 100, "superficie": 0,
"nox": 25.0, "pm10": 8.0, "pm25": 5.0, "ges": 150.0, "code_pcaet": "02"
}
})
nox_density = emission_no_area.get_emission_density('nox')
print(f" Densité NOx: {nox_density} (superficie = 0)")
print()
# Cas 4: Secteur inconnu
print("4. Secteur d'émission non référencé:")
emission_unknown_sector = EmissionData({
"type": "Feature",
"properties": {
"aasqa": "99", "name": "Zone test", "population": 5000, "superficie": 10.0,
"nox": 35.0, "pm10": 12.0, "pm25": 8.0, "ges": 200.0, "code_pcaet": "99"
}
})
print(f" Code secteur: {emission_unknown_sector.code_pcaet}")
print(f" Nom secteur: {emission_unknown_sector.get_secteur_name()}")
print()
def main():
"""Fonction principale de démonstration"""
print("SCRIPT DE DÉMONSTRATION - CLASSE EMISSIONDATA")
print("=" * 60)
print("Ce script teste toutes les fonctionnalités de la classe EmissionData")
print("pour l'analyse des données d'émissions atmosphériques.")
print()
try:
# Exécution de toutes les démonstrations
demo_basic_properties()
demo_emission_analysis()
demo_density_calculations()
demo_per_capita_calculations()
demo_secteur_analysis()
demo_coordinate_functions()
demo_inherited_methods()
demo_comparative_analysis()
demo_edge_cases()
print("=" * 60)
print("RÉCAPITULATIF DES MÉTHODES TESTÉES")
print("=" * 60)
print("Méthodes spécifiques à EmissionData:")
print("✓ get_emission_density(pollutant)")
print("✓ get_emission_per_capita(pollutant)")
print("✓ get_total_emissions()")
print("✓ get_secteur_name()")
print()
print("Méthodes héritées de AtmoDataBase:")
print("✓ get_aasqa_name()")
print("✓ get_source()")
print("✓ has_coordinates()")
print("✓ get_emoji_by_level(level, style)")
print("✓ get_color_by_level(level)")
print()
print("Propriétés testées:")
print("✓ Coordonnées géographiques et distance")
print("✓ Émissions par polluant (nox, pm10, pm25, ges)")
print("✓ Données démographiques (population, superficie)")
print("✓ Secteurs d'émission (code_pcaet)")
print("✓ Gestion des cas particuliers (données manquantes)")
print()
print("✅ TOUTES LES FONCTIONNALITÉS ONT ÉTÉ TESTÉES AVEC SUCCÈS")
except Exception as e:
print(f"❌ ERREUR lors de l'exécution: {e}")
raise
if __name__ == "__main__":
main()

View file

@ -0,0 +1,536 @@
#!/usr/bin/env python3
"""
Script de démonstration des fonctionnalités de la classe EpisodePollution
=====================================================================
Ce script illustre l'utilisation de toutes les méthodes disponibles
dans la classe EpisodePollution pour analyser les épisodes de pollution.
Fonctionnalités testées:
- Analyse des alertes de pollution
- Identification des polluants responsables
- Analyse des niveaux d'alerte (Information/Alerte)
- Gestion des géométries complexes
- Analyse des zones géographiques affectées
- Utilisation des méthodes héritées de la classe de base
"""
import json
from pathlib import Path
from atmo_wrapper import AtmoDataWrapper
from atmo_data_wrapper import EpisodePollution, AtmoDataCollection, Coordinates
from atmo_data_wrapper import CODE_POLLUANT
def demo_basic_properties():
"""Démonstration des propriétés de base d'un objet EpisodePollution"""
print("=" * 60)
print("DÉMONSTRATION DES PROPRIÉTÉS DE BASE")
print("=" * 60)
# Exemple d'épisode de pollution aux particules fines
sample_episode = {
"type": "Feature",
"geometry": {
"type": "MultiPolygon",
"coordinates": [[[
[6.0, 49.0], [6.5, 49.0], [6.5, 49.5], [6.0, 49.5], [6.0, 49.0]
]]]
},
"properties": {
"aasqa": "90",
"source": "ATMO Grand Est",
"date_maj": "2024-01-15T10:00:00",
"lib_zone": "Agglomération de Nancy",
"code_pol": "5",
"lib_pol": "PM10",
"code_zone": "54395",
"date_dif": "2024-01-15",
"date_ech": "2024-01-16",
"etat": "PROCEDURE D'INFORMATION ET DE RECOMMANDATION"
}
}
episode = EpisodePollution(sample_episode)
print(f"Zone affectée: {episode.lib_zone}")
print(f"Code polluant: {episode.code_pol}")
print(f"Polluant: {episode.lib_pol}")
print(f"Code polluant normalisé: {episode.get_polluant_code()}")
print(f"État: {episode.etat}")
print(f"AASQA: {episode.get_aasqa_name()}")
print(f"Source: {episode.get_source()}")
print(f"Date de diffusion: {episode.date_dif}")
print(f"Date d'échéance: {episode.date_ech}")
print(f"Date de mise à jour: {episode.date_maj}")
print(f"Géométrie complexe: {episode.is_geometry_complex()}")
if episode.has_coordinates():
print(f"Coordonnées: {episode.coordinates}")
else:
print("Pas de coordonnées ponctuelles (géométrie de zone)")
print(f"Représentation: {episode}")
print()
def demo_alert_analysis():
"""Démonstration de l'analyse des alertes"""
print("=" * 60)
print("ANALYSE DES ALERTES DE POLLUTION")
print("=" * 60)
# Exemples d'épisodes avec différents niveaux d'alerte
episodes_samples = [
{
"properties": {
"aasqa": "18", "lib_zone": "Île-de-France", "code_pol": "5", "lib_pol": "PM10",
"etat": "PROCEDURE D'INFORMATION ET DE RECOMMANDATION", "date_dif": "2024-01-15"
}
},
{
"properties": {
"aasqa": "18", "lib_zone": "Paris", "code_pol": "3", "lib_pol": "O3",
"etat": "PROCEDURE D'ALERTE", "date_dif": "2024-07-20"
}
},
{
"properties": {
"aasqa": "90", "lib_zone": "Strasbourg", "code_pol": "1", "lib_pol": "NO2",
"etat": "PAS DE DEPASSEMENT", "date_dif": "2024-01-15"
}
},
{
"properties": {
"aasqa": "13", "lib_zone": "Marseille", "code_pol": "6", "lib_pol": "PM2.5",
"etat": "PROCEDURE D'INFORMATION", "date_dif": "2024-03-10"
}
},
{
"properties": {
"aasqa": "84", "lib_zone": "Lyon", "code_pol": "3", "lib_pol": "O3",
"etat": "ALERTE NIVEAU 1", "date_dif": "2024-08-15"
}
}
]
print("Analyse des différents types d'alertes:")
print()
for i, episode_data in enumerate(episodes_samples, 1):
episode_data["type"] = "Feature"
episode = EpisodePollution(episode_data)
print(f"{i}. {episode.lib_zone} - {episode.lib_pol}")
print(f" État: {episode.etat}")
print(f" Alerte active: {'' if episode.is_alert_active() else ''}")
print(f" Niveau d'alerte: {episode.get_alert_level()}")
print(f" Code polluant: {episode.get_polluant_code()}")
print(f" Date: {episode.date_dif}")
print()
def demo_pollutant_analysis():
"""Démonstration de l'analyse par polluant"""
print("=" * 60)
print("ANALYSE PAR POLLUANT")
print("=" * 60)
# Exemples avec différents polluants
pollutants_samples = [
{"code_pol": "1", "lib_pol": "NO2", "zone": "Zone urbaine dense"},
{"code_pol": "2", "lib_pol": "SO2", "zone": "Zone industrielle"},
{"code_pol": "3", "lib_pol": "O3", "zone": "Zone péri-urbaine"},
{"code_pol": "5", "lib_pol": "PM10", "zone": "Centre-ville"},
{"code_pol": "6", "lib_pol": "PM2.5", "zone": "Zone trafic"}
]
print("Polluants détectés dans les épisodes:")
print()
for i, poll_data in enumerate(pollutants_samples, 1):
episode_data = {
"type": "Feature",
"properties": {
"aasqa": "99", "lib_zone": poll_data["zone"],
"code_pol": poll_data["code_pol"], "lib_pol": poll_data["lib_pol"],
"etat": "PROCEDURE D'INFORMATION", "date_dif": "2024-01-15"
}
}
episode = EpisodePollution(episode_data)
polluant_description = CODE_POLLUANT.get(episode.get_polluant_code(), "Description non disponible")
print(f"{i}. Polluant: {episode.lib_pol} (Code: {episode.code_pol})")
print(f" Code normalisé: {episode.get_polluant_code()}")
print(f" Zone: {episode.lib_zone}")
print(f" Description: {polluant_description}")
print()
def demo_geometry_analysis():
"""Démonstration de l'analyse des géométries"""
print("=" * 60)
print("ANALYSE DES GÉOMÉTRIES")
print("=" * 60)
# Épisode avec géométrie simple (Point)
episode_point = EpisodePollution({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [2.3522, 48.8566]
},
"properties": {
"aasqa": "18", "lib_zone": "Paris Centre", "code_pol": "3", "lib_pol": "O3",
"etat": "PROCEDURE D'INFORMATION", "date_dif": "2024-07-15"
}
})
# Épisode avec géométrie complexe (MultiPolygon)
episode_multipolygon = EpisodePollution({
"type": "Feature",
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[[[2.0, 48.5], [2.5, 48.5], [2.5, 49.0], [2.0, 49.0], [2.0, 48.5]]],
[[[2.8, 48.7], [3.2, 48.7], [3.2, 49.1], [2.8, 49.1], [2.8, 48.7]]]
]
},
"properties": {
"aasqa": "18", "lib_zone": "Île-de-France", "code_pol": "5", "lib_pol": "PM10",
"etat": "PROCEDURE D'ALERTE", "date_dif": "2024-01-20"
}
})
# Épisode sans géométrie
episode_no_geom = EpisodePollution({
"type": "Feature",
"properties": {
"aasqa": "90", "lib_zone": "Grand Est", "code_pol": "1", "lib_pol": "NO2",
"etat": "PAS DE DEPASSEMENT", "date_dif": "2024-01-15"
}
})
episodes = [
("Point", episode_point),
("MultiPolygon", episode_multipolygon),
("Sans géométrie", episode_no_geom)
]
print("Types de géométries dans les épisodes:")
print()
for i, (type_geom, episode) in enumerate(episodes, 1):
print(f"{i}. {type_geom} - {episode.lib_zone}")
print(f" Type géométrie: {episode.geometry.get('type', 'Non défini')}")
print(f" Géométrie complexe: {'' if episode.is_geometry_complex() else ''}")
print(f" A des coordonnées: {'' if episode.has_coordinates() else ''}")
if episode.has_coordinates():
print(f" Coordonnées: {episode.coordinates}")
print(f" État: {episode.etat}")
print()
def demo_temporal_analysis():
"""Démonstration de l'analyse temporelle des épisodes"""
print("=" * 60)
print("ANALYSE TEMPORELLE DES ÉPISODES")
print("=" * 60)
# Simulation d'épisodes à différentes dates
temporal_episodes = [
{
"date_dif": "2024-01-15", "date_ech": "2024-01-16",
"lib_zone": "Lyon", "lib_pol": "PM10", "etat": "PROCEDURE D'INFORMATION",
"aasqa": "84", "code_pol": "5"
},
{
"date_dif": "2024-07-20", "date_ech": "2024-07-21",
"lib_zone": "Marseille", "lib_pol": "O3", "etat": "PROCEDURE D'ALERTE",
"aasqa": "13", "code_pol": "3"
},
{
"date_dif": "2024-12-05", "date_ech": "2024-12-07",
"lib_zone": "Strasbourg", "lib_pol": "NO2", "etat": "PROCEDURE D'INFORMATION",
"aasqa": "90", "code_pol": "1"
}
]
print("Épisodes chronologiques:")
print()
for i, episode_data in enumerate(temporal_episodes, 1):
episode_data["type"] = "Feature"
episode = EpisodePollution(episode_data)
# Calcul de la durée (simplifié)
try:
from datetime import datetime
date_debut = datetime.strptime(episode.date_dif, "%Y-%m-%d")
date_fin = datetime.strptime(episode.date_ech, "%Y-%m-%d")
duree = (date_fin - date_debut).days
except:
duree = "Non calculable"
print(f"{i}. {episode.lib_zone} - {episode.lib_pol}")
print(f" Période: du {episode.date_dif} au {episode.date_ech}")
print(f" Durée: {duree} jour(s)" if duree != "Non calculable" else f" Durée: {duree}")
print(f" État: {episode.etat}")
print(f" Niveau: {episode.get_alert_level()}")
print()
def demo_regional_analysis():
"""Démonstration de l'analyse régionale des épisodes"""
print("=" * 60)
print("ANALYSE RÉGIONALE DES ÉPISODES")
print("=" * 60)
# Simulation d'épisodes dans différentes régions
regional_episodes = [
{"aasqa": "18", "region": "Île-de-France", "lib_zone": "Paris", "lib_pol": "PM10"},
{"aasqa": "84", "region": "Auvergne-Rhône-Alpes", "lib_zone": "Lyon", "lib_pol": "O3"},
{"aasqa": "13", "region": "Provence-Alpes-Côte d'Azur", "lib_zone": "Marseille", "lib_pol": "PM2.5"},
{"aasqa": "90", "region": "Grand Est", "lib_zone": "Strasbourg", "lib_pol": "NO2"},
{"aasqa": "59", "region": "Hauts-de-France", "lib_zone": "Lille", "lib_pol": "O3"}
]
print("Épisodes par région AASQA:")
print()
for i, episode_data in enumerate(regional_episodes, 1):
episode_data.update({
"type": "Feature",
"code_pol": "5", "etat": "PROCEDURE D'INFORMATION", "date_dif": "2024-01-15"
})
episode = EpisodePollution(episode_data)
print(f"{i}. Région: {episode_data['region']}")
print(f" AASQA: {episode.get_aasqa_name()}")
print(f" Zone: {episode.lib_zone}")
print(f" Polluant: {episode.lib_pol}")
print(f" Code AASQA: {episode.aasqa}")
print()
def demo_inherited_methods():
"""Démonstration des méthodes héritées de la classe de base"""
print("=" * 60)
print("MÉTHODES HÉRITÉES DE LA CLASSE DE BASE")
print("=" * 60)
episode_data = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [4.8357, 45.7640]
},
"properties": {
"aasqa": "84",
"source": "ATMO Auvergne-Rhône-Alpes",
"date_maj": "2024-07-15T14:30:00",
"lib_zone": "Lyon Métropole",
"code_pol": "3",
"lib_pol": "O3",
"etat": "PROCEDURE D'ALERTE",
"date_dif": "2024-07-15",
"date_ech": "2024-07-16"
}
}
episode = EpisodePollution(episode_data)
print(f"Zone affectée: {episode.lib_zone}")
print(f"AASQA: {episode.get_aasqa_name()}")
print(f"Source: {episode.get_source()}")
print(f"A des coordonnées: {'' if episode.has_coordinates() else ''}")
if episode.has_coordinates():
print(f"Coordonnées: {episode.coordinates}")
print()
# Test des fonctions de couleur et emoji (niveau fictif pour démonstration)
print("Fonctions de couleur et emoji (exemple avec niveau 4 - Alerte):")
test_level = 4 # Niveau d'alerte
couleur_hex, couleur_rgb = episode.get_color_by_level(test_level)
emoji_round = episode.get_emoji_by_level(test_level, "round")
emoji_square = episode.get_emoji_by_level(test_level, "square")
print(f" - Couleur hex: {couleur_hex}")
print(f" - Couleur RGB: {couleur_rgb}")
print(f" - Emoji rond: {emoji_round}")
print(f" - Emoji carré: {emoji_square}")
print()
def demo_comparative_analysis():
"""Démonstration d'une analyse comparative entre épisodes"""
print("=" * 60)
print("ANALYSE COMPARATIVE DES ÉPISODES")
print("=" * 60)
episodes_data = [
{
"type": "Feature",
"properties": {
"aasqa": "18", "lib_zone": "Paris", "code_pol": "5", "lib_pol": "PM10",
"etat": "PROCEDURE D'INFORMATION ET DE RECOMMANDATION", "date_dif": "2024-01-15"
}
},
{
"type": "Feature",
"properties": {
"aasqa": "84", "lib_zone": "Lyon", "code_pol": "3", "lib_pol": "O3",
"etat": "PROCEDURE D'ALERTE", "date_dif": "2024-07-20"
}
},
{
"type": "Feature",
"properties": {
"aasqa": "13", "lib_zone": "Marseille", "code_pol": "6", "lib_pol": "PM2.5",
"etat": "ALERTE NIVEAU 1", "date_dif": "2024-03-10"
}
},
{
"type": "Feature",
"properties": {
"aasqa": "90", "lib_zone": "Strasbourg", "code_pol": "1", "lib_pol": "NO2",
"etat": "PAS DE DEPASSEMENT", "date_dif": "2024-05-05"
}
}
]
episodes = [EpisodePollution(ep) for ep in episodes_data]
print("Comparaison des épisodes de pollution:")
print("-" * 70)
print(f"{'Zone':<15} {'Polluant':<8} {'Niveau':<12} {'Alerte':<8} {'État'}")
print("-" * 70)
for episode in episodes:
alerte_status = "" if episode.is_alert_active() else ""
niveau = episode.get_alert_level()
print(f"{episode.lib_zone:<15} {episode.lib_pol:<8} {niveau:<12} {alerte_status:<8} {episode.etat}")
print()
# Statistiques
total_episodes = len(episodes)
alertes_actives = sum(1 for ep in episodes if ep.is_alert_active())
print(f"Statistiques:")
print(f" - Total d'épisodes: {total_episodes}")
print(f" - Alertes actives: {alertes_actives}")
print(f" - Pourcentage d'alertes: {(alertes_actives/total_episodes)*100:.1f}%")
# Répartition par polluant
polluants = {}
for episode in episodes:
polluant = episode.get_polluant_code()
polluants[polluant] = polluants.get(polluant, 0) + 1
print(f" - Répartition par polluant:")
for polluant, count in polluants.items():
print(f" * {polluant}: {count} épisode(s)")
print()
def demo_edge_cases():
"""Démonstration de la gestion des cas particuliers"""
print("=" * 60)
print("GESTION DES CAS PARTICULIERS")
print("=" * 60)
# Cas 1: Épisode sans état défini
print("1. Épisode sans état défini:")
episode_no_state = EpisodePollution({
"type": "Feature",
"properties": {
"aasqa": "99", "lib_zone": "Zone test", "code_pol": "5", "lib_pol": "PM10",
"etat": "", "date_dif": "2024-01-15"
}
})
print(f" État: '{episode_no_state.etat}'")
print(f" Alerte active: {'' if episode_no_state.is_alert_active() else ''}")
print(f" Niveau d'alerte: {episode_no_state.get_alert_level()}")
print()
# Cas 2: Code polluant non standard
print("2. Code polluant non référencé:")
episode_unknown_pollutant = EpisodePollution({
"type": "Feature",
"properties": {
"aasqa": "99", "lib_zone": "Zone test", "code_pol": "99", "lib_pol": "Polluant inconnu",
"etat": "PROCEDURE D'INFORMATION", "date_dif": "2024-01-15"
}
})
print(f" Code polluant original: {episode_unknown_pollutant.code_pol}")
print(f" Code polluant normalisé: {episode_unknown_pollutant.get_polluant_code()}")
print(f" Nom polluant: {episode_unknown_pollutant.lib_pol}")
print()
# Cas 3: Géométrie malformée
print("3. Géométrie non standard:")
episode_no_geometry = EpisodePollution({
"type": "Feature",
"geometry": None,
"properties": {
"aasqa": "99", "lib_zone": "Zone sans géométrie", "code_pol": "3", "lib_pol": "O3",
"etat": "PROCEDURE D'ALERTE", "date_dif": "2024-01-15"
}
})
print(f" Géométrie: {episode_no_geometry.geometry}")
print(f" Type géométrie: {episode_no_geometry.geometry.get('type', 'Non défini') if episode_no_geometry.geometry else 'None'}")
print(f" Géométrie complexe: {'' if episode_no_geometry.is_geometry_complex() else ''}")
print(f" A des coordonnées: {'' if episode_no_geometry.has_coordinates() else ''}")
print()
def main():
"""Fonction principale de démonstration"""
print("SCRIPT DE DÉMONSTRATION - CLASSE EPISODEPOLLUTION")
print("=" * 60)
print("Ce script teste toutes les fonctionnalités de la classe EpisodePollution")
print("pour l'analyse des épisodes de pollution atmosphérique.")
print()
try:
# Exécution de toutes les démonstrations
demo_basic_properties()
demo_alert_analysis()
demo_pollutant_analysis()
demo_geometry_analysis()
demo_temporal_analysis()
demo_regional_analysis()
demo_inherited_methods()
demo_comparative_analysis()
demo_edge_cases()
print("=" * 60)
print("RÉCAPITULATIF DES MÉTHODES TESTÉES")
print("=" * 60)
print("Méthodes spécifiques à EpisodePollution:")
print("✓ get_polluant_code()")
print("✓ is_alert_active()")
print("✓ get_alert_level()")
print("✓ is_geometry_complex()")
print()
print("Méthodes héritées de AtmoDataBase:")
print("✓ get_aasqa_name()")
print("✓ get_source()")
print("✓ has_coordinates()")
print("✓ get_emoji_by_level(level, style)")
print("✓ get_color_by_level(level)")
print()
print("Propriétés testées:")
print("✓ Codes et noms des polluants")
print("✓ États et niveaux d'alerte")
print("✓ Zones géographiques affectées")
print("✓ Géométries (Point, MultiPolygon)")
print("✓ Informations temporelles (dates)")
print("✓ Gestion des cas particuliers")
print()
print("✅ TOUTES LES FONCTIONNALITÉS ONT ÉTÉ TESTÉES AVEC SUCCÈS")
except Exception as e:
print(f"❌ ERREUR lors de l'exécution: {e}")
raise
if __name__ == "__main__":
main()

213
demos/demo_licence_atmo.py Normal file
View file

@ -0,0 +1,213 @@
#!/usr/bin/env python3
"""
Démonstration des fonctions de licence Atmo France
=================================================
Ce script montre comment utiliser les fonctions utilitaires pour afficher
la mention légale requise par Atmo France selon leur licence d'utilisation
des données en open data sous licence ODbL.
Conformément aux exigences d'Atmo France:
"Chacun peut donc bénéficier gratuitement de ces données mises en open data
sous licence ODbL, en indiquant la source "Atmo France et les Associations
agréées de surveillance de la qualité de l'air" ou "Atmo France / AASQA"
dans sa version courte."
"""
import sys
import os
# Ajouter le répertoire parent au PYTHONPATH pour importer le package local
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from atmo_data_wrapper import (
get_atmo_licence,
print_atmo_licence,
ATMO_LICENCE_COURTE,
ATMO_LICENCE_LONGUE,
ATMO_LICENCE_COMPLETE
)
def demo_licence_formats():
"""Démonstration des différents formats de licence"""
print("📋 FORMATS DE LICENCE ATMO FRANCE")
print("=" * 50)
print()
print("1⃣ Version courte (recommandée pour citations):")
print(f"{get_atmo_licence('courte')}")
print()
print("2⃣ Version longue (nom officiel complet):")
print(f"{get_atmo_licence('longue')}")
print()
print("3⃣ Version complète (avec détails de licence):")
print(get_atmo_licence('complete'))
print()
def demo_usage_examples():
"""Exemples d'utilisation pratique"""
print("💡 EXEMPLES D'UTILISATION PRATIQUE")
print("=" * 50)
print()
print("1⃣ Dans un script de données:")
print("```python")
print("from atmo_data_wrapper import get_atmo_licence")
print()
print("# Récupérer des données")
print("indices = client.get_indices_atmo()")
print()
print("# Afficher la source")
print('print(f"Source: {get_atmo_licence(\'courte\')}")')
print("```")
print()
print("Résultat:")
print(f"Source: {get_atmo_licence('courte')}")
print()
print("2⃣ Dans un rapport ou documentation:")
print("```python")
print("print_atmo_licence('complete')")
print("```")
print()
print("Résultat:")
print_atmo_licence('complete')
print()
print("3⃣ Accès direct aux constantes:")
print("```python")
print("from atmo_data_wrapper import ATMO_LICENCE_COURTE")
print("print(ATMO_LICENCE_COURTE)")
print("```")
print()
print("Résultat:")
print(ATMO_LICENCE_COURTE)
print()
def demo_integration_examples():
"""Exemples d'intégration dans différents contextes"""
print("🔧 EXEMPLES D'INTÉGRATION")
print("=" * 50)
print()
print("1⃣ Dans un fichier CSV:")
print("# Commentaire en en-tête de fichier CSV")
print(f"# Source: {get_atmo_licence('longue')}")
print("# Licence: ODbL")
print("commune,indice_atmo,date")
print("Nancy,2,2024-01-15")
print("...")
print()
print("2⃣ Dans un graphique matplotlib:")
print("```python")
print("import matplotlib.pyplot as plt")
print("from atmo_data_wrapper import get_atmo_licence")
print()
print("# Créer le graphique")
print("plt.plot(dates, indices)")
print("plt.title('Évolution de la qualité de l\\'air')")
print()
print("# Ajouter la source")
print("plt.figtext(0.02, 0.02, f'Source: {get_atmo_licence(\'courte\')}', fontsize=8)")
print("plt.show()")
print("```")
print()
print("3⃣ Dans une API REST:")
print("```python")
print("from flask import Flask, jsonify")
print("from atmo_data_wrapper import get_atmo_licence")
print()
print("@app.route('/api/air-quality')")
print("def get_air_quality():")
print(" data = get_air_quality_data()")
print(" return jsonify({")
print(" 'data': data,")
print(" 'source': get_atmo_licence('courte'),")
print(" 'licence': 'ODbL'")
print(" })")
print("```")
print()
print("4⃣ Dans un footer HTML:")
print("```html")
print("<footer>")
print(f" <p>Source des données: {get_atmo_licence('longue')}</p>")
print(" <p>Licence: <a href='https://opendatacommons.org/licenses/odbl/'>ODbL</a></p>")
print("</footer>")
print("```")
print()
def demo_licence_compliance():
"""Vérification de conformité avec les exigences"""
print("✅ CONFORMITÉ AVEC LES EXIGENCES ATMO FRANCE")
print("=" * 55)
print()
print("📋 Exigences officielles:")
print('• Indiquer la source "Atmo France et les Associations agréées"')
print('• Ou version courte "Atmo France / AASQA"')
print('• Respecter la licence ODbL')
print()
print("✅ Notre implémentation:")
print(f"• Version courte: '{get_atmo_licence('courte')}'")
print(f"• Version longue: '{get_atmo_licence('longue')}'")
print("• Mention de la licence ODbL incluse")
print("• URLs officielles fournies")
print()
print("🎯 Recommandations d'usage:")
print("• Utiliser la version courte pour les citations courtes")
print("• Utiliser la version longue pour les documents officiels")
print("• Utiliser la version complète pour les mentions légales détaillées")
print("• Toujours mentionner la licence ODbL")
print()
def main():
"""Fonction principale"""
print("DÉMONSTRATION DES FONCTIONS DE LICENCE ATMO FRANCE")
print("=" * 60)
print("Conformité avec les exigences de licence ODbL d'Atmo France")
print()
try:
demo_licence_formats()
demo_usage_examples()
demo_integration_examples()
demo_licence_compliance()
print("=" * 60)
print("✅ DÉMONSTRATION TERMINÉE AVEC SUCCÈS")
print()
print("🔑 Fonctions disponibles:")
print(" • get_atmo_licence(format): Retourne la licence selon le format")
print(" • print_atmo_licence(format): Affiche la licence")
print()
print("📋 Formats supportés:")
print("'courte': Version abrégée")
print("'longue': Version officielle complète")
print("'complete': Avec détails de licence")
print()
print("📁 Constantes disponibles:")
print(" • ATMO_LICENCE_COURTE")
print(" • ATMO_LICENCE_LONGUE")
print(" • ATMO_LICENCE_COMPLETE")
except Exception as e:
print(f"❌ Erreur lors de l'exécution: {e}")
import traceback
print("\nDétails de l'erreur:")
print(traceback.format_exc())
if __name__ == "__main__":
main()

View file

@ -0,0 +1,333 @@
#!/usr/bin/env python3
"""
Script de démonstration de toutes les fonctions du datamodel IndicePollen
Utilise les données réelles de l'API pour Tomblaine (aujourd'hui)
Idéal pour documentation et exemples
"""
from atmo_data_wrapper import AtmoDataClient, AtmoDataException
from atmo_data_wrapper import CODE_TAXON
from datetime import datetime
import sys
def demo_pollen_functions():
"""Démonstration complète des fonctions IndicePollen avec données réelles"""
# Configuration
CODE_INSEE_TOMBLAINE = "54526"
AASQA_GRAND_EST = "44"
today = datetime.now().strftime('%Y-%m-%d')
print("🌸 DÉMONSTRATION DES FONCTIONS INDICEPOLLEN")
print("=" * 55)
print(f"📍 Ville: Tomblaine (INSEE: {CODE_INSEE_TOMBLAINE})")
print(f"🗓️ Date: {today}")
print(f"🏛️ AASQA: {AASQA_GRAND_EST} (Grand Est)")
print()
try:
# Connexion et récupération des données
print("🔐 Connexion à l'API...")
client = AtmoDataClient()
success = client.auto_login()
if not success:
print("❌ Échec de la connexion à l'API")
return False
print("✅ Connecté avec succès !")
# Récupération des données pollen
print(f"🌸 Récupération des indices pollen pour {today}...")
pollens = client.get_indices_pollens(
format="geojson",
date=today,
aasqa=AASQA_GRAND_EST,
code_zone=CODE_INSEE_TOMBLAINE,
with_geom=False
)
if not pollens or len(pollens) == 0:
print("❌ Aucune donnée pollen trouvée")
return False
print(f"{len(pollens)} station(s) trouvée(s)")
# Utiliser la première station pour la démo
pollen = pollens[0]
print(f"📍 Station sélectionnée: {pollen.lib_zone}")
print()
# === DÉMONSTRATION DES PROPRIÉTÉS DE BASE ===
print("📊 === PROPRIÉTÉS DE BASE (héritées d'AtmoDataBase) ===")
print()
print("🏛️ INFORMATIONS GÉNÉRALES:")
print(f" • AASQA: {pollen.aasqa}")
print(f" • Zone: {pollen.lib_zone}")
print(f" • Source: {pollen.source}")
print(f" • Alerte active: {pollen.alerte}")
print()
print("🗺️ COORDONNÉES:")
if pollen.has_coordinates():
coords = pollen.get_coordinates()
print(f" • Latitude: {coords.latitude:.6f}")
print(f" • Longitude: {coords.longitude:.6f}")
else:
print(" • Pas de coordonnées disponibles")
print()
print("🎨 FONCTIONS CENTRALISÉES:")
for level in [0, 1, 2, 3, 4]:
emoji_round = pollen.get_emoji_by_level(level, "round")
emoji_square = pollen.get_emoji_by_level(level, "square")
color_hex, color_rgb = pollen.get_color_by_level(level)
print(f" • Niveau {level}: {emoji_round}{emoji_square} {color_hex} {color_rgb}")
print()
# === DÉMONSTRATION DES PROPRIÉTÉS SPÉCIFIQUES POLLEN ===
print("🌿 === PROPRIÉTÉS SPÉCIFIQUES POLLEN ===")
print()
print("📈 CODES PAR TAXON:")
print(f" • Ambroisie (ambr): {pollen.code_ambr}")
print(f" • Armoise (arm): {pollen.code_arm}")
print(f" • Aulne (aul): {pollen.code_aul}")
print(f" • Bouleau (boul): {pollen.code_boul}")
print(f" • Graminées (gram): {pollen.code_gram}")
print(f" • Olivier (oliv): {pollen.code_oliv}")
print()
print("🔬 CONCENTRATIONS (grains/m³):")
print(f" • Ambroisie: {pollen.conc_ambr:.1f}")
print(f" • Armoise: {pollen.conc_arm:.1f}")
print(f" • Aulne: {pollen.conc_aul:.1f}")
print(f" • Bouleau: {pollen.conc_boul:.1f}")
print(f" • Graminées: {pollen.conc_gram:.1f}")
print(f" • Olivier: {pollen.conc_oliv:.1f}")
print()
print("🎯 TAXONS RESPONSABLES DE L'INDICE:")
print(f" • pollen_resp (raw): '{pollen.pollen_resp}'")
print()
# === DÉMONSTRATION DES MÉTHODES HELPER ===
print("🛠️ === MÉTHODES HELPER ===")
print()
# 1. is_alert_active()
print("🚨 1. DÉTECTION D'ALERTE:")
alert_active = pollen.is_alert_active()
print(f" • is_alert_active(): {alert_active}")
if alert_active:
print(" → Alerte pollen active !")
else:
print(" → Pas d'alerte pollen")
print()
# 2. get_highest_pollen()
print("🏆 2. POLLEN LE PLUS ÉLEVÉ:")
highest_taxon, highest_code = pollen.get_highest_pollen()
highest_name = CODE_TAXON.get(highest_taxon, highest_taxon.title())
print(f" • get_highest_pollen(): ('{highest_taxon}', {highest_code})")
print(f" → Espèce: {highest_name}")
print(f" → Niveau: {highest_code}")
print()
# 3. get_highest_concentration()
print("🔬 3. CONCENTRATION LA PLUS ÉLEVÉE:")
highest_conc_taxon, highest_conc_value = pollen.get_highest_concentration()
highest_conc_name = CODE_TAXON.get(highest_conc_taxon, highest_conc_taxon.title())
print(f" • get_highest_concentration(): ('{highest_conc_taxon}', {highest_conc_value})")
print(f" → Espèce: {highest_conc_name}")
print(f" → Concentration: {highest_conc_value:.1f} grains/m³")
print()
# 4. get_dangerous_pollens()
print("⚠️ 4. POLLENS DANGEREUX (niveau ≥ 4):")
dangerous = pollen.get_dangerous_pollens()
print(f" • get_dangerous_pollens(): {dangerous}")
if dangerous:
dangerous_names = [CODE_TAXON.get(p, p.title()) for p in dangerous]
print(f" → Espèces à risque: {', '.join(dangerous_names)}")
else:
print(" → Aucun pollen à risque élevé")
print()
# 5. get_responsible_pollens() - NOUVELLE MÉTHODE
print("🎯 5. TAXONS RESPONSABLES DE L'INDICE (API):")
responsible = pollen.get_responsible_pollens()
print(f" • get_responsible_pollens(): {responsible}")
if responsible:
print(f" → Espèces responsables selon l'API: {', '.join(responsible)}")
else:
print(" → Aucun taxon responsable spécifié par l'API")
print()
# 6. get_concentrations()
print("📊 6. TOUTES LES CONCENTRATIONS:")
concentrations = pollen.get_concentrations()
print(f" • get_concentrations(): {concentrations}")
print(" → Détail:")
for taxon, conc in concentrations.items():
taxon_name = CODE_TAXON.get(taxon, taxon.title())
print(f" - {taxon_name}: {conc:.1f} grains/m³")
print()
# 7. get_pollens_summary()
print("📋 7. RÉSUMÉ COMPLET:")
summary = pollen.get_pollens_summary()
print(f" • get_pollens_summary():")
print(" → Structure complète par taxon:")
for code_taxon, info in summary.items():
if info['code'] > 0: # Afficher seulement les pollens détectés
print(f" - {code_taxon}:")
print(f" * Code: {info['code']}")
print(f" * Espèce: {info['espece']}")
print(f" * Qualificatif: {info['qualificatif']}")
print(f" * Concentration: {info['concentration']:.1f} gr/m³")
print(f" * Couleur: {info['couleur']}")
print(f" * Émoji rond: {info['emoji_round']}")
print(f" * Émoji carré: {info['emoji_square']}")
print()
# 8. Test des styles d'émojis
print("🎨 8. TEST DES STYLES D'ÉMOJIS:")
summary_round = pollen.get_pollens_summary("round")
summary_square = pollen.get_pollens_summary("square")
print(" • Comparaison des styles par défaut:")
for code_taxon in ['arm', 'gram']: # Tester avec les pollens détectés
if summary_round[code_taxon]['code'] > 0:
round_emoji = summary_round[code_taxon]['emoji']
square_emoji = summary_square[code_taxon]['emoji']
espece = summary_round[code_taxon]['espece']
print(f" - {espece}: Rond={round_emoji} | Carré={square_emoji}")
print()
# === DÉMONSTRATION DES MÉTHODES STRING ===
print("📝 === REPRÉSENTATION STRING ===")
print()
print("🔤 MÉTHODE __str__():")
print(f" • str(pollen): '{str(pollen)}'")
print()
# === EXEMPLES D'UTILISATION PRATIQUE ===
print("💡 === EXEMPLES D'UTILISATION PRATIQUE ===")
print()
print("🎯 ANALYSE RAPIDE:")
print(f" • Niveau global le plus élevé: {highest_name} (niveau {highest_code})")
print(f" • Concentration maximale: {highest_conc_name} ({highest_conc_value:.1f} gr/m³)")
if alert_active:
print(" • ⚠️ ALERTE ACTIVE - Précautions recommandées")
if dangerous:
print(f" • 🚨 POLLENS À RISQUE: {', '.join([CODE_TAXON.get(p, p) for p in dangerous])}")
else:
print(" • ✅ Aucun pollen à risque élevé")
print()
print("📈 DÉTECTION DE TENDANCES:")
detected_pollens = [taxon for taxon, info in summary.items() if info['code'] > 0]
significant_pollens = [taxon for taxon, info in summary.items() if info['code'] >= 2]
print(f" • Pollens détectés: {len(detected_pollens)} espèces")
print(f" • Pollens significatifs (≥2): {len(significant_pollens)} espèces")
if significant_pollens:
sig_names = [CODE_TAXON.get(p, p) for p in significant_pollens]
print(f"{', '.join(sig_names)}")
print()
print("🔍 FILTRAGE AVANCÉ:")
# Exemple de filtrage par concentration
high_conc = {t: c for t, c in concentrations.items() if c > 5.0}
if high_conc:
print(" • Concentrations élevées (>5 gr/m³):")
for taxon, conc in high_conc.items():
taxon_name = CODE_TAXON.get(taxon, taxon.title())
print(f"{taxon_name}: {conc:.1f} gr/m³")
else:
print(" • Aucune concentration élevée (>5 gr/m³)")
print()
# === INFORMATIONS TECHNIQUES ===
print("🔧 === INFORMATIONS TECHNIQUES ===")
print()
print("📦 STRUCTURE DE DONNÉES:")
print(f" • Type d'objet: {type(pollen).__name__}")
print(f" • Classe parente: {type(pollen).__bases__[0].__name__}")
print(f" • Propriétés disponibles: {len(pollen.properties)} champs")
print(f" • Géométrie: {'Oui' if pollen.has_coordinates() else 'Non'}")
print()
print("🎨 MÉTHODES HÉRITÉES:")
inherited_methods = [
'get_emoji_by_level()', 'get_color_by_level()', 'has_coordinates()',
'get_coordinates()', 'get_source()'
]
print(f" • Méthodes de AtmoDataBase: {', '.join(inherited_methods)}")
print()
specific_methods = [
'is_alert_active()', 'get_highest_pollen()', 'get_highest_concentration()',
'get_dangerous_pollens()', 'get_responsible_pollens()', 'get_concentrations()', 'get_pollens_summary()'
]
print(f" • Méthodes spécifiques IndicePollen: {', '.join(specific_methods)}")
print()
print("🎨 NOUVEAUTÉS ÉMOJIS:")
print(" • get_emoji_by_level(level, style) - style='round'|'square'")
print(" • get_emoji(style) - pour IndiceAtmo avec choix de style")
print(" • get_pollens_summary(emoji_style) - résumé avec style d'émoji")
print(" • Chaque résumé inclut emoji_round ET emoji_square")
print()
print("📋 CONFORMITÉ NOTICE OFFICIELLE (1er avril 2025):")
print(" • Tous les champs de la notice officielle sont supportés")
print(" • Classes IndiceAtmo et IndicePollen conformes aux spécifications")
print(" • Nouvelles propriétés pour IndiceAtmo : type_zone, coordonnées réglementaires")
print(" • Concentrations facultatives ajoutées selon la notice")
print(" • Méthodes basées sur les règles officielles de calcul")
print(" • Codes couleur et qualificatifs conformes au tableau page 6")
print()
print("✅ === DÉMONSTRATION TERMINÉE ===")
print()
print("📚 Ce script illustre toutes les fonctionnalités de la classe IndicePollen")
print("🔧 Utilisez ces exemples pour votre documentation et vos développements")
print()
return True
except AtmoDataException as e:
print(f"❌ Erreur API: {e}")
return False
except Exception as e:
print(f"❌ Erreur inattendue: {e}")
import traceback
traceback.print_exc()
return False
def main():
"""Point d'entrée principal"""
print("🌸 Démonstration des fonctions IndicePollen")
print("=" * 55)
print()
success = demo_pollen_functions()
if not success:
print("\n❌ La démonstration s'est terminée avec des erreurs")
sys.exit(1)
else:
print("🎉 Démonstration terminée avec succès !")
sys.exit(0)
if __name__ == "__main__":
main()