URL: https://linuxfr.org/users/purplepsycho/journaux/openstreetmap-et-gps-garmin
Title: Openstreetmap et GPS garmin
Authors: purplepsycho
Date: 2022年06月13日T23:32:10+02:00
License: CC By-SA
Tags: garmin, openstreetmap, js et firefox
Score: 49
Hello
En passant, je vous présente un petit projet sur lequel je bosse quand j'ai un peu de temps. Bonne lecture.
## Contexte
En 2013, une moule avait posté un chouette post sur l'utilisation d'un montre garmin sous linux : [garmin-forerunner-110](https://linuxfr.org/users/delavergne/journaux/garmin-forerunner-110-sous-linux).
Comme je suis un peu un mouton qui adore réinventer la roue, je me suis dis que j'allais faire pareil, avec une montre similaire : la garmin forerunner 10.
Pour faire court, quand on va courir, la montre enregistre un fichier `.FIT` récupérable simplement par connexion USB (le système de fichier monté est du `FAT32`).
## Objectif
Avec ce projet, je voulais un truc simple: une liste de sorties sur lesquelles je peux cliquer, et l'affichage de la course sélectionnée sur un fond de carte.
Comme souvent, sur ce genre de projet, je tente d'apprendre des trucs, comme ça fait longtemps que je commencé, j'ai eu le temps de tester plein de trucs :
* awk
* bash
* python
* automake
* gtk
* openstreetmap
* ...
Mais j'y reviendrai, peut être.
Après avoir cherché et testé différents concepts, je me suis arrété sur le suivant, à savoir utiliser [`leaflet.js`](https://leafletjs.com) qui permet d'afficher simplement des données json sur une carte [openstreetmap](https://www.openstreetmap.org)
Pour cela, il faut
* convertir certaines informations des enregistrements en json (yaka)
* afficher le tout sur le fond de carte (fokon)
## Fokon : utiliser OSM à la maison
Pour gribouiller sur des cartes, on utilise donc leaflet.js.
C'est pas trop compliqué, tout tiens dans un fichier html (dont beaucoup de javascript) :
* On crée un `div` qui contiendra la carte, on l'appelle `'map'` (on force sa taille à 100%)
* On crée une carte `L.map` qu'on place dans le `div` précédent
* On crée un chemin `L.geoJSON` qu'on place sur la carte. Le format geojson est documenté là : [geojson.org].
Un petit truc qui m'a géné : les paramètres de latitude et longitude semblent inversé entre L.map et L.geoJSON, dans le premier, on donne latitude, longitude, dans le second, longitude, latitude...
Voici donc un petit fichier HTML qui fait ça, on notera que j'ai mis en local les fichier leaflet.[js,css] pour simplifier les tests, mais on peut les remplacer par un cdn.
```html
```
Si le fichier est ouvert sur un navigateur, il devrait faire un petit rectangle autour d'une station sur une petite ile australe...
### Tadaa

On sait maintenant à quoi doit ressembler notre yaka : quelque chose qui transforme les `.FIT` en geojson.
## Yaka : de fit à geojson.
Ici, on a deux solutions, on peut utiliser [fitdump](https://pub.ks-and-ks.ne.jp/cycling/fitdump.shtml) et décoder le contenu avec awk (comme dans le premier journal). L'inconvénient de cette solution et qu'il faut l'adapter à chaque montre : le code des messages change en fonction des paramètres que peut stocquer la montre. La modification n'est pas énorme, c'est simplement le code du `Local message type` qui est à revoir, mais bon...
On on peut s'amuser à faire une bibliotèque pour travailler avec les fichiers `.FIT` et un programme que l'utilise pour générer du json.
Le format `.FIT` est bien décrit par garmin, leur SDK propose pas mal de choses là dessus.
Un `.FIT`, c'est une succession d'enregistrements. On en a de deux types:
* Les définitions,
* Les données.
Les définitions décrivent comment sont organisées les données, elles contiennent :
* le type de données (parmis un choix donné)
* les champs contenus dans l'enregistrement.
Ok, comme ça c'est pas clair... Disons que dans un fichier, on peut avoir un message disant :
"À partir de maintenant, les données de type `FIT_GLOBAL_RECORD` contiennent un champ latitude et un champ longitude"
Les données sont variables, on sait ce qu'elle contiennent grace aux définitions.
### libfit: lire les `.FIT`
Implémenter une specification, c'est pas très marrant à décrire, je vous passe donc les détails, mais on peut tout trouver ici: [libfit](https://framagit.org/mabu/libfit)
C'est une petite bibliothèque qui propose quelques fonctions qui font ce qu'on veut:
* `FIT_open`: ouvre le fichier `.FIT` (en vérifiant au passage sa cohérence)
* `FIT_readRecord`: lit le prochain enregistrement disponible
* `FIT_decodeValues`: décode les valeurs contenues dans un enregistrement.
### Et générer du geojson
Couplée à [libjson-c](https://github.com/json-c/json-c/wiki), on a tout ce qu'il faut pour créer un convertisseur qui tient en 64 lignes:
```c
#include
#include
#include "libfit.h"
#include "libfit-messages.h"
#define die(...) do { fprintf(stderr, __VA_ARGS__); exit(1); } while(0)
int main(int argc, char *argv[]) {
if (argc != 3) {
die("usage %s ", *argv);
}
/* open the fit file */
fit_file_t *f;
fit_record_t *rec = NULL;
f = FIT_open(argv[1]);
if (NULL == f)
die("cannot dump %s\n", argv[1]);
/* prepare the json file */
struct json_object *obj, *path, *coordinates;
obj = json_object_new_object();
path = json_object_new_object();
json_object_object_add(path, "type", json_object_new_string("LineString"));
coordinates = json_object_new_array();
/* convert */
for (;;) {
double lat, lon;
/* read next record */
fit_record_t *rc = FIT_readRecord(f, rec);
if (!rc) {
FIT_free(rec);
break;
}
rec = rc;
/* only kee global record */
if (FIT_GLOBAL_RECORD != FIT_getRecordType(rec))
continue;
/* decode position and speed */
if (0 == FIT_decodeValues(rec, FIT_GLOBAL_RECORD,
FIT_RECORD_POSITION_LONG_S32, &lon,
FIT_RECORD_POSITION_LAT_S32, &lat,
-1)) {
struct json_object * xy = json_object_new_array();
json_object_array_add(xy, json_object_new_double(lon));
json_object_array_add(xy, json_object_new_double(lat));
json_object_array_add(coordinates, xy);
}
}
FIT_close(f);
/* finalize */
json_object_object_add(path, "coordinates", coordinates);
json_object_object_add(obj, "path", path);
/* save json file */
json_object_to_file(argv[2], obj);
}
```
### Un peu de mise en forme
On se retrouve avec un fichier json de 11 ko (pour un fit de 8), pas génial de mettre ça directement dans le fichier index.html. Il est assez facile de créer une fonction qui récupère le contenu d'un json via `fetch`, mais le problème de cette méthode est qu'elle impose la mise en place d'un serveur http, car firefox interdit les [COR](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSRequestNotHttp), comme j'ai envie de rester simple pour cette note, on va s'en passer. (Notez que c'est facile de mettre en place un serveur qui fait le job: `python -m http.server` suffit)
On commence par transformer notre `json` en `js`. Sachant que le fichier json tient sur une seule ligne, on peut se contenter d'inserer un peu de code en tête:
```bash
sed 's/^/var data = /' test.json> test.js
```
Voilà, on peut alors charger notre fichier via une balise `
```
Au lieu de donner des coordonnées en dur, cette nouvelle variable est utilisée pour centrer le plan et dessiner le chemin :
```html
map = L.map('map', {
center: [data.path.coordinates[0][1], data.path.coordinates[0][0]],
zoom: 13,
layers: [layer_osm]
});
path = L.geoJSON(data.path),
```
### Et voilà

## Les sources
Toutes les sources ici : [libfit](https://framagit.org/mabu/libfit)
### Et après ?
Dans un autre [repo](https://framagit.org/mabu/BasicFitConvert), j'ai codé un truc plus complet, dans lesquels on retrouve :
* Le calcul de la hauteur à partir de la position GPS (merci [elevation](https://pypi.org/project/elevation/))
* L'affichage de la vitesse à l'aide de [Chart.js](https://www.chartjs.org/)
* Afficher plusieurs trace en même temps, pour faire comme une heatmap...
P'tet qu'un jour, je documenterai ça