Seconde SNT
Localisation, cartographie et mobilité

Décoder une trame NMEA

Prise en main d'un notebook

cliquer ici pour faire apparaître le détail



**Un notebook est une suite de cellules** contenant, soit du **texte**, soit du **code**. - Les cellules de code sont facilement reconnaissables, elles sont précédées de `Entrée [ ]:` - Pour **exécuter** le code Python d'une **cellule**, il faut frapper **``** (ou cliquer sur le bouton , ou encore utiliser le menu `Cellules>Exécuter les cellules...`). - Si l'instruction est exécutée, les crochets de "Entrée[1]" devraient contenir un numéro et non une étoile "Entrée[*]".
Ce notebook a été réalisé par Andjekel 2023-2024. Source © Franck CHEVRIER 2019-2021
Les activités sur Capytale sont sous licence Creative Commons.
NOMS : CORRECTION

1. Lecture et exploitation d'une trame NMEA

1.1. La trame NMEA

Pour transmettre des informations de géolocalisation, on utilise des trames (paquets) dont la forme est normalisée. La norme utilisée est la norme NMEA 0183 (norme spécifique notamment pour la communication entre équipements marins, contrôlée par la National Marine Electronics Association (USA))

Aujourd'hui, les smartphones sont tous dotés d'un récepteur GPS. Ils peuvent se géolocaliser grâce à cette puce, c'est-à-dire connaître leurs positions géographiques. L'objectif de cette activité est de comprendre le fonctionnement de ce dispositif.</span>

Une trame NMEA est une suite de caractères produite par un récepteur GPS. Cette suite de catactères contient des informations de géolocalisation, comme :

  • l'heure de réception
  • la latitude
  • la longitude
  • l'altitude
  • le nombre de satellites utilisés pour le calcul
  • etc.
    Les quinze composantes de la trame sont séparées par une virgule.</span>

Exemple de trame :

`$GPGGA,064036.289,4836.5375,N,00740.9373,E,1,04,3.2,200.2,M,,,,,0000*0E` est un exemple de trame NMEA.


Les deux premiers caractères après le signe $ identifient l'origine du signal.

Les principaux préfixes sont :

  • BD ou GB - Beidou (Chine) ;
  • GA - Galileo (Europe) ;
  • GP - GPS (USA) ;
  • GL - GLONASS (Russie) ;
  • GN - signaux mixés GPS et GLONASS.

Décodons cette trame :

caractères signification
$ Début d'une nouvelle trame
GP Origine du signal : GP pour GPS, GA pour Galiléo, BD ou GB pour Beidu, GL pour Glonass, GN pour GPS + Glonass
GGA Type de trame : GGA pour position (fixe), RMC pour navidation (déplacement), ...
064036.289 Heure de réception : ici à 06h 40m 36,289s (heure UTC)
4836.5375 Latitude : ici 48° 36.5375' (DM) = 48.608958° (DD) = 48° 36' 32.25" (DMS)
N N pour latitude nord, S pour latitude sud
00740.9373 Longitude : ici 7° 40.9373' (DM) = 7.682288° (DD) = 7° 40' 56.238" (DMS)
E E pour longitude est, W pour longitude ouest
1 Positionnement : 0 pour calé, 1 pour positionné, 2 en mode différentiel, 6 pour estimée
04 Nombre de satellites utilisés pour calculer les coordonnées : ici 4
3.2 Précision horizontale : de 1.0 (optimale) à 8.0 non fiable
200.2,M Altitude : ici 200.2 mètres
,,,,,0000 D'autres informations peuvent être inscrites dans ces champs
*0E Caractères de contrôle

TrameNMEA.png

1.2. Analyse d'une trame NMEA

Objectif : obtenir des fonctions Python qui permettent d'automatiser le déchiffrement d'une telle trame.

L'instruction Python ci-dessous permet d'affecter la trame NMEA à la variable trame_brute. Cela permet de mettre la trame en mémoire.

Pour ce TP, nous allons utiliser la trame ci-dessous :

`$GPGGA,064036.289,2053.026627,S,05526.895820,E,1,08,3.2,162.2,M,,,,,0000*11`

In [1]:
# Exécuter cette cellule

trame_brute ='$GPGGA,064036.289,2053.026627,S,05526.895820,E,1,08,3.2,162.2,M,,,,,0000*11'

trame_brute #pour afficher le contenu mémoire de trame_brute
Out[1]:
'$GPGGA,064036.289,2053.026627,S,05526.895820,E,1,08,3.2,162.2,M,,,,,0000*11'

La trame GPS est pour l'instant codée sous forme d'une chaine de caractère (str). Exécuter l'instruction ci-dessous qui va permettre d'obtenir les différentes données séparées par des virgules dans une liste (list).
L'instruction split permet de découper une chaîne de caractères.

In [2]:
# Exécuter cette cellule

trame = trame_brute.split(",") #création d'une liste (éléments identifiés par une séparation par virgule)

trame #pour afficher le contenu mémoire de trame
Out[2]:
['$GPGGA',
 '064036.289',
 '2053.026627',
 'S',
 '05526.895820',
 'E',
 '1',
 '08',
 '3.2',
 '162.2',
 'M',
 '',
 '',
 '',
 '',
 '0000*11']

Au final, la trame est codée sous forme d'une liste, dont chaque élément est une chaine de caractères.

les listes

Structure d'une liste :
  • les éléments sont entre deux crochets ([ ]),
  • et séparés par une virgule [...,...,...]
Un des gros avantages d'une liste est que vous pouvez appeler ses éléments par leur position.
Ce numéro est appelé **indice** (ou **index**) de la liste. | | | | | | | |:--- |:---:|:---:|:---:|:---:|:---:| |liste |:| ["girafe",| "tigre",| "singe",| "souris"]| |indice |:| 0 | 1 | 2 | 3| La commande liste[ ] permet d’accéder facilement à un élément d’une liste grâce à son indice. Soyez très attentif au fait que les **indices** d'une liste de n éléments **commencent à 0** et se terminent à n-1. Voyez l'exemple suivant :
>>> animaux = ["girafe", "tigre", "singe", "souris"] #animaux est une liste de 4 éléments >>> animaux[0] #extraction de l'élément d'index 0 'girafe' >>> animaux[1] #extraction de l'élément d'index 1 'tigre' >>> animaux[3] 'souris'

Par conséquent, si on appelle l'élément d'indice 4 de notre liste, Python renverra un message d'erreur :

>>> animaux[4]
Traceback (most recent call last): File "<input>", line 2, in <module> IndexError: list index out of range

Pour accéder aux différents éléments de la trame, on utilise la syntaxe trame[k] pour accéder au kème élément de la trame. (rappel : les éléments sont numérotés à partir de 0)

1. Saisir l'instruction nécessaire pour obtenir l'altitude.

In [3]:
# Saisir l'instruction pour obtenir l'altitude
trame[9]
Out[3]:
'162.2'

2. Saisir l'instruction nécessaire pour obtenir l'altitude avec son unité à la suite.

In [4]:
# Saisir l'instruction pour obtenir l'altitude et son unité
trame[9] + " " + trame[10]
Out[4]:
'162.2 M'
Pour afficher deux chaînes côte-à-côte, il faut les "rassembler" en un seul objet : cette opération s'appelle la concaténation. En Python, elle peut être réalisée avec le symbole + placé entre les deux élements. **remarque** _Si vous souhaitez concaténer une chaîne et un nombre, tel qu’un entier ou un flottant à virgule flottante, convertissez le nombre en chaîne avec str(), puis utilisez l’opérateur + : chaîne + str(nombre)_

3. Exécuter l'instruction suivante et compléter les commentaires.

In [5]:
trame[7] # trame[7] permet d'obtenir "le nombre de satellites"
Out[5]:
'08'

4. Tester les saisies suivantes, compléter les commentaires et proposer une saisie.

In [6]:
trame[0]
Out[6]:
'$GPGGA'
In [7]:
trame[0][1:3] #permet de récupérer "le type de signal ici GPS 'américain)'
Out[7]:
'GP'
In [8]:
#écrire une saisie permettant de récupérer l'information 'GGA' sur le type de trame.
trame[0][3:]
Out[8]:
'GGA'

Extraire une sous-chaîne à l’aide du découpage de chaînes en Python

Au code ci-dessus, nous allons ajouter des crochets [] à la fin de la variable stockant la chaîne pour préciser quels caractères nous souhaitons extraire. Le format des crochets doit être [start : stop : step] (séparés par deux points (:)). start et stop représentent les index (positions) de départ et de fin de la sous-chaîne à extraire et step le pas. exemple : la sous-chaîne retournée est en fait entre l’index start et l’index stop - 1 car l’indexation commence à 0 en Python. Donc, si nous voulons récupérer Miss de Mississippi, nous devrions utiliser [0 : 4] Les crochets ne peuvent pas être vides. Si le pas n'est pas précisé, sa valeur par défaut est 1. Si vous souhaitez utiliser les valeurs par défaut, le nombre requis de deux points : doit être ajouté avec des espaces entre les deux pour indiquer le paramètre auquel vous faites référence. Reportez-vous à la liste suivante pour une meilleure compréhension.
[:] -> Renvoie la chaîne entière. [4 : ] -> Renvoie une sous-chaîne à partir de l’index 4 jusqu’au dernier index. [ : 2] -> Renvoie une sous-chaîne à partir de l’index 0 jusqu’à l’index 2 exclu. [2 : 4] -> Renvoie une sous-chaîne à partir de l’index 2 jusqu’à l’index 4 exclu.

1.3. Conversion de l'heure

Objectif : la date doit être convertie pour s'afficher dans le format **06 h 40 min 36 s 289**

Saisir l'instruction nécessaire pour obtenir l'heure.

In [9]:
heure = trame[1]
heure
Out[9]:
'064036.289'

Le format ne convient pas puisqu'il est HHMMSS,SSS.
Il faut donc extraire les deux premiers chiffres pour obtenir l'heure, puis les 3ème et 4ème pour les minutes et ceux qui restent représentent les secondes (en décimal).

In [10]:
heure = trame[1][:2]
heure
Out[10]:
'06'

La valeur qui est extraite est une chaîne de caractères (string ou str). Cela peut se vérifier avec la commande type(trame[1][:2]) qui renvoie class 'str', ce qui explique la présence de guillemets.

Il est préférable de la convertir en nombre. L'heure est un entier , donc on peut utiliser la commande int(trame[1][:2]).

In [11]:
heure = int(trame[1][:2])
heure
Out[11]:
6

comment utiliser l'indexation d'une chaîne de caractères ?

cliquer ici pour faire apparaître l'aide

On accède aux différents caractères de l'heure $064036.289$ par leur position ou index. Les différents caractères de l'heure sont indexés de la façon suivante : | | | | | | | | | | | | | |:--- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |heure |:|0|6|4|0|3|6|.|2|8|9| |indice |:|0|1|2|3|4|5|6|7|8|9| | | |H |H | M | M | s | s | . | s | s | s |

Les deux premiers caractères repésentent les heures, les 3ème et 4ème les minutes et les autres les secondes

Saisir l'instruction nécessaire pour obtenir les minutes.

In [12]:
minutes = int(trame[1][2:4])
minutes
Out[12]:
40

Saisir l'instruction nécessaire pour obtenir les secondes.
Attention, les secondes sont en format nombre réel (float).

In [13]:
secondes= float(trame[1][4:])
secondes
Out[13]:
36.289

Il est maintenant possible d'afficher l'heure d'enregistrement de la trame NMEA au format 06 h 40 min 36 s 289.
Remarque : pour concaténer tous ces éléments de types différents (int (entier), float (réel) et str (chaîne de caractères)), il faut les convertir en chaîne de caractères avec str.

In [14]:
print("La trame a été enregistrée à "+str(heure)+" h "+str(minutes)+" min "+str(secondes)+" s")
La trame a été enregistrée à 6 h 40 min 36.289 s

1.4. Latitude et longitude

Objectif : pouvoir utiliser la latitude et la longitude dans un autre format compatible avec une application de création de carte

Saisir l'instruction nécessaire pour obtenir la latitude.

In [15]:
# Saisir l'instruction pour obtenir la latitude
trame[2]
Out[15]:
'2053.026627'

Dans la trame initiale le codage $2053.026627$ correspond à $20° 53.026627'$ ( ' signifie minute, c'est à dire $\frac{1}{60}$ de degré) et on convertit ainsi en degré : $20 + \dfrac{53.026627}{60} = 20.88377°$. On ajoute un signe - car on est dans l'hemisphère sud. On obtient : -20.88377°

On donne la fonction suivante, qui permet d'obtenir la latitude de la trame en degré.
L'exécuter, puis écrire la fonction longitude qui permet d'obtenir la longitude en degré.

In [16]:
def latitude(trame):
    "fonction qui donne la latitude en degré décimal avec cardinalité"
    lat_dm = trame[2]
    lat_deg = int(lat_dm[:2])           # récupération du nombre entier de degré (ce sont les deux premiers chiffres)
    lat_min = float(lat_dm[2:])         # récupération du nombre de minutes sous forme d'un nombre float à virgule
    lat_final = lat_deg + lat_min/60    # calcul final avec conversion des minutes en degrés
    lat_card = trame[3]                 # récupération de la cardinalité (N ou S)
    if lat_card == "S":
        lat_card_signe = -1
    else :
        lat_card_signe = 1
    lat_final = lat_final * lat_card_signe

    return lat_final
In [17]:
#Ecrire ici une fonction qui donne la longitude en degré décimal avec cardinalité

def longitude(trame):
    "fonction qui donne la longitude en degré décimal avec cardinalité"
    lon_dm = trame[4]
    lon_deg = int(lon_dm[:3])           # récupération du nombre entier de degré (ce sont les deux premiers chiffres)
    lon_min = float(lon_dm[3:])         # récupération du nombre de minutes sous forme d'un nombre float à virgule
    lon_final = lon_deg + lon_min/60    # calcul final avec conversion des minutes en degrés
    lon_card = trame[5]                 # récupération de la cardinalité (N ou S)
    if lon_card == "W":
        lon_card_signe = -1
    else :
        lon_card_signe = 1
    lon_final = lon_final * lon_card_signe

    return lon_final

Tester ces deux fonctions avec les instructions suivantes .

In [18]:
longitude(trame)
Out[18]:
55.44826366666667
In [19]:
latitude(trame)
Out[19]:
-20.883777116666668

1.5. Association avec une carte géographique

Objectif : créer une carte centrée sur les coordonnées de la trame GPS

Nous allons utiliser la bibliothèque folium qui permet de créer une carte.
Cette bibliothèque dispose d'outils permettant de créer une **carte interactive** basée sur un fond cartographique.
Nous allons utiliser le fond cartographique gratuit et collaboratif **OpenStreetMap**.

Il faut d'abord importer le module folium import folium.
On créé une carte avec l'instruction folium.Map :
- il faut indiquer les coordonnées du centre de la carte et
- le niveau de zoom avec lequel elle va s'afficher (de 1 à 18). folium.Map( location, zoom_start)
  1. Tester l'exemple ci-dessous.
In [21]:
import folium
# définir les coordonnées du centre de la carte
latitude = 48.6360
longitude = -1.5114

# créer la carte en indiquant les coordonnées du centre et un niveau de zoom, entre 1 (planisphère) et 18 (bâtiment)
carte1 = folium.Map(location=[latitude, longitude],zoom_start = 16)

# affichage de l'objet carte1 que l'on vient de créer
carte1
Out[21]:
  1. Compléter le code ci-dessous pour qu'une carte s'affiche, centrée sur les coordonnées de la trame GPS.
In [20]:
''' Penser à réexécuter les les fonctions latitude et longitude '''

import folium
# définir les coordonnées du centre de la carte
latitude = latitude(trame)
longitude = longitude(trame)


# créer la carte en indiquant les coordonnées du centre et un niveau de zoom, entre 1 (planisphère) et 18 (bâtiment)
carte1 = folium.Map(location=[latitude, longitude],zoom_start = 18)
carte1.add_child(folium.LatLngPopup())
folium.Marker(location=[latitude, longitude], 
              popup = "Vous êtes ici",
              icon=folium.Icon(color='red',icon='info-sign')
             ).add_to(carte1)

# affichage de l'objet carte1 que l'on vient de créer
carte1
Out[20]:
Donner l'endroit où vous êtes : AU Lycée levavasseur, devant la porte de la salle D14

Pour limiter le risque d'erreurs, il est conseillé de rassembler toutes les instructions nécessaires dans la même cellule de code :

  • mise en mémoire de la trame,
  • découpage de la trame et
  • les fonctions de conversion des coordonnées géographiques.

En bonus et si vous avez encore du temps

## 2. Comment la trame permet de résoudre l'enquête ### Résolution d'une énigme
Arthur et Mathilde sont convoqués au bureau du CPE car ils ont été pointés absents à leur cours de 11h30-12h25.
Passionné d'informatique, le CPE leur demande de lui montrer leur téléphone.
Après avoir cherché dans les fichiers enregistrés, voici ce qu'il y trouve :
pour Mathilde : $ \rm \$GNGGA,114430.249,4911.0400,N,00022.7460,W,0,0,,84.3,M,47.1,M,,*7B$
et pour Arthur : $ \rm \$GNGGA,113550.295,4911.1841,N,00022.825,W,0,0,,84.2,M,47.3,M,,*7B$.

Aidez le CPE à décoder ces informations pour savoir où étaient Mathilde et Arthur.

In [ ]:
 
Mathilde était ... et Arthur était ...

A la fin de la séance, bien penser à enregistrer puis cliquer sur Rendre le devoir.

Votre page pourra alors être commentée et notée par le professeur