Matthieu
Maritime R/O et futur Jambon de Pays
Programme en Python3 avec des libs :
pip install Geopy matplotlib datetime logging
pip install iri2016 (nternational Reference Ionosphere )
Code : Tout sélectionner
import math
from geopy.distance import geodesic
import matplotlib.pyplot as plt
from datetime import datetime
import logging
from iri2016 import IRI
# Configuration du logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# Constantes
RAYON_TERRE = 6371 # Rayon moyen de la Terre en kilomètres
HAUTEUR_BASE_IONOSPHERE = 300 # Hauteur de base de l'ionosphère en kilomètres
def calculer_distance(coords_emetteur, coords_recepteur):
"""
Calcule la distance entre deux coordonnées géographiques.
Paramètres :
coords_emetteur (tuple) : Latitude et longitude de l'émetteur.
coords_recepteur (tuple) : Latitude et longitude du récepteur.
Retourne :
float : Distance en kilomètres.
"""
try:
return geodesic(coords_emetteur, coords_recepteur).km
except ValueError as e:
logging.error(f"Erreur lors du calcul de la distance : {e}")
return None
def calculer_angle_elevation(distance, hauteur_ionosphere):
"""
Calcule l'angle d'élévation pour une onde radio.
Paramètres :
distance (float) : Distance entre l'émetteur et le récepteur.
hauteur_ionosphere (float) : Hauteur de l'ionosphère.
Retourne :
float : Angle d'élévation en degrés.
"""
try:
angle_incidence_rad = math.asin(distance / (2 * (RAYON_TERRE + hauteur_ionosphere)))
return 90 - math.degrees(angle_incidence_rad)
except ValueError as e:
logging.error(f"Erreur lors du calcul de l'angle d'élévation : {e}")
return None
def calculer_distance_saut(hauteur_ionosphere, angle_elevation_deg):
"""
Calcule la distance de saut d'une onde radio.
Paramètres :
hauteur_ionosphere (float) : Hauteur de l'ionosphère.
angle_elevation_deg (float) : Angle d'élévation en degrés.
Retourne :
float : Distance de saut en kilomètres.
"""
try:
angle_incidence_rad = math.radians(90 - angle_elevation_deg)
return 2 * (RAYON_TERRE + hauteur_ionosphere) * math.sin(angle_incidence_rad)
except ValueError as e:
logging.error(f"Erreur lors du calcul de la distance de saut : {e}")
return None
def calculer_nombre_rebonds(distance_totale, distance_saut):
"""
Calcule le nombre de rebonds nécessaires pour couvrir une distance totale.
Paramètres :
distance_totale (float) : Distance totale entre l'émetteur et le récepteur.
distance_saut (float) : Distance de saut de l'onde radio.
Retourne :
int : Nombre de rebonds.
"""
try:
return math.ceil(distance_totale / distance_saut)
except ZeroDivisionError as e:
logging.error(f"Erreur lors du calcul du nombre de rebonds : {e}")
return None
def frequence_critique(densite_electronique):
"""
Calcule la fréquence critique basée sur la densité électronique.
Paramètres :
densite_electronique (float) : Densité électronique.
Retourne :
float : Fréquence critique en MHz.
"""
try:
return 9 * math.sqrt(densite_electronique) * 1e-6
except ValueError as e:
logging.error(f"Erreur lors du calcul de la fréquence critique : {e}")
return None
def frequence_muf(f_critique, angle_elevation_deg):
"""
Calcule la Fréquence Maximale Utilisable (MUF).
Paramètres :
f_critique (float) : Fréquence critique.
angle_elevation_deg (float) : Angle d'élévation en degrés.
Retourne :
float : MUF en MHz.
"""
try:
return f_critique / math.cos(math.radians(angle_elevation_deg))
except ZeroDivisionError as e:
logging.error(f"Erreur lors du calcul de la MUF : {e}")
return None
def plot_zone_reception(coords_emetteur, distance_saut):
"""
Trace la zone de réception du signal radio.
Paramètres :
coords_emetteur (tuple) : Latitude et longitude de l'émetteur.
distance_saut (float) : Distance de saut de l'onde radio.
"""
try:
fig, ax = plt.subplots()
ax.scatter(coords_emetteur[1], coords_emetteur[0], color='red', label='Émetteur')
circle = plt.Circle(coords_emetteur[::-1], distance_saut, color='blue', fill=False, linestyle='--', label='Zone de Réception')
ax.add_patch(circle)
ax.set_xlim(coords_emetteur[1] - 1.5 * distance_saut, coords_emetteur[1] + 1.5 * distance_saut)
ax.set_ylim(coords_emetteur[0] - 1.5 * distance_saut, coords_emetteur[0] + 1.5 * distance_saut)
ax.set_xlabel('Longitude')
ax.set_ylabel('Latitude')
ax.set_title('Zone de Réception du Signal Radio')
ax.legend()
plt.grid(True)
plt.show()
except Exception as e:
logging.error(f"Erreur lors du tracé de la zone de réception : {e}")
def ajuster_densite_electronique(heure, saison, indice_f107):
"""
Ajuste la densité électronique en fonction de l'heure, de la saison et de l'activité solaire.
Paramètres :
heure (int) : Heure de la journée.
saison (str) : Saison ("été" ou "hiver").
indice_f107 (float) : Indice d'activité solaire.
Retourne :
float : Facteur de densité électronique ajusté.
"""
try:
facteur_jour = 1.2 if 6 <= heure <= 18 else 1.0
facteur_saison = 1.1 if saison == "été" else 1.0
facteur_solaire = 1 + 0.05 * (indice_f107 - 70) / 100
return facteur_jour * facteur_saison * facteur_solaire
except Exception as e:
logging.error(f"Erreur lors de l'ajustement de la densité électronique : {e}")
return None
def obtenir_donnees_iri_local(latitude, longitude, heure, date):
"""
Obtient les données IRI en utilisant la bibliothèque locale IRI-2016.
Paramètres :
latitude (float) : Latitude de l'émetteur.
longitude (float) : Longitude de l'émetteur.
heure (int) : Heure de la journée.
date (str) : Date au format AAAA-MM-JJ.
Retourne :
dict : Données IRI incluant la hauteur de l'ionosphère.
"""
try:
# Initialisation du modèle IRI
iri = IRI()
# Configuration des paramètres
iri.set_date_time(date, heure)
iri.set_geographic(latitude, longitude)
# Exécution du modèle
result = iri.run()
# Extraction de la hauteur de l'ionosphère
hauteur_ionosphere = result['hmF2'] # Exemple d'extraction, à adapter
return {'hauteur_ionosphere': hauteur_ionosphere}
except Exception as e:
logging.error(f"Erreur lors de la récupération des données IRI locales : {e}")
return None
def validate_coordinates(latitude, longitude):
"""
Valide les valeurs de latitude et de longitude.
Paramètres :
latitude (float) : Valeur de la latitude.
longitude (float) : Valeur de la longitude.
Retourne :
bool : True si valide, False sinon.
"""
if not (-90 <= latitude <= 90):
logging.error("Latitude invalide. Doit être comprise entre -90 et 90.")
return False
if not (-180 <= longitude <= 180):
logging.error("Longitude invalide. Doit être comprise entre -180 et 180.")
return False
return True
def validate_heure(heure):
"""
Valide l'heure de la journée.
Paramètres :
heure (int) : Heure de la journée.
Retourne :
bool : True si valide, False sinon.
"""
if not (0 <= heure < 24):
logging.error("Heure invalide. Doit être comprise entre 0 et 23.")
return False
return True
def main():
try:
lat_emetteur = float(input("Entrez la latitude de l'émetteur : "))
lon_emetteur = float(input("Entrez la longitude de l'émetteur : "))
lat_recepteur = float(input("Entrez la latitude du récepteur : "))
lon_recepteur = float(input("Entrez la longitude du récepteur : "))
if not (validate_coordinates(lat_emetteur, lon_emetteur) and validate_coordinates(lat_recepteur, lon_recepteur)):
return
print("Sélectionnez le type d'antenne : ")
print("1. Filaire (Dipôle)")
print("2. Yagi")
print("3. Log-Périodique")
print("4. Verticale")
print("5. Boucle")
type_antenne = int(input("Entrez le numéro correspondant au type d'antenne : "))
if type_antenne not in [1, 2, 3, 4, 5]:
logging.error("Type d'antenne invalide. Veuillez sélectionner un nombre entre 1 et 5.")
return
coords_emetteur = (lat_emetteur, lon_emetteur)
coords_recepteur = (lat_recepteur, lon_recepteur)
heure = datetime.now().hour
if not validate_heure(heure):
return
saison = "été" if 6 <= datetime.now().month <= 8 else "hiver"
indice_f107 = 150 # Peut être configurable
# Utilisation de la date actuelle pour les données en temps réel
date_actuelle = datetime.now().strftime('%Y-%m-%d')
# Tentative d'obtenir les données IRI
donnees_iri = obtenir_donnees_iri_local(lat_emetteur, lon_emetteur, heure, date_actuelle)
hauteur_ionosphere = donnees_iri['hauteur_ionosphere'] if donnees_iri else HAUTEUR_BASE_IONOSPHERE
densite_electronique = 1e12 * ajuster_densite_electronique(heure, saison, indice_f107)
distance_totale = calculer_distance(coords_emetteur, coords_recepteur)
angle_elevation = calculer_angle_elevation(distance_totale, hauteur_ionosphere)
if type_antenne in [2, 3]:
angle_elevation = min(angle_elevation, 15)
distance_saut = calculer_distance_saut(hauteur_ionosphere, angle_elevation)
nombre_rebonds = calculer_nombre_rebonds(distance_totale, distance_saut)
f_critique = frequence_critique(densite_electronique)
muf = frequence_muf(f_critique, angle_elevation)
print(f"Distance entre l'émetteur et le récepteur : {distance_totale:.2f} km")
print(f"Angle d'élévation optimal : {angle_elevation:.2f} degrés")
print(f"Nombre de rebonds nécessaires : {nombre_rebonds}")
print(f"Fréquence critique : {f_critique:.2f} MHz")
print(f"Fréquence maximale utilisable (MUF) : {muf:.2f} MHz")
plot_zone_reception(coords_emetteur, distance_saut)
except ValueError as e:
logging.error(f"Entrée invalide : {e}")
except Exception as e:
logging.error(f"Une erreur inattendue s'est produite : {e}")
if __name__ == "__main__":
main()