Améliorer la précision GPS en JavaScript
03 Feb 2013
En particulier, il existe une API JavaScript de géolocalisation permettant de récupérer ou de suivre, la position GPS d’un téléphone mobile depuis une page Web.
Malheureusement, la position récupérée n’est pas toujours de bonne qualité.
Cet article présente une méthode pur JavaScript pour améliorer la précision des coordonnées récupérées.
Si l'API JavaScript est assez basique (nous le verrons plus loin) elle a le mérite d'être supporté par la plupart des navigateurs mobile (et desktop) modernes :

Comme je le disais en préambule, l’écart entre la position de l’utilisateur et celle renvoyée par le navigateur peut être très grand. Cela rend inutilisable cette API dans beaucoup de contextes où la précision a de l’importance : comme la recherche d’objets autour de soi.
Le Contexte : mobi.tisseo.fr
J’ai mis au point cette méthode pour la WebApp mobi.tisseo.fr dont je suis responsable technique chez Tisséo.
Cette application (site mobile) permet entre autre, la récupération de la liste des arrêts de bus et tramways autour de la position du téléphone mobile :

Pour cette fonctionnalité, il est primordial que la précision de géolocalisation soit de 150 mètres ou moins. Notre parti pris est même de ne donner aucun résultat si on ne parvient pas à descendre en dessous de 166 mètres. (ce qui sera le cas sur un Android avec le GPS désactivé) :
Votre position n'a pu être déterminée avec une précision suffisante.
Nous avons éprouvé notre code de géolocalisation sur un large panel de téléphone de test : IPhone3/4/5, IPad, XPeria X10, Galaxy Note, HTC Desire HD, Samsung Bada, Nokia WindowsPhone 7, …
Les défauts de l'API JavaScript
Pour que vous puissiez comprendre, je vous présente l’API JavaScript et vous explique les problèmes rencontrés.
Seulement deux fonctions sont disponibles :
- getCurrentPosition : permet de récupérer la position GPS une seule fois
- WatchPosition : permet de suivre la position GPS à intervalle régulier
getCurrentPosition
C’est cette fonction qui donne les plus mauvais résultats.
Elle dispose de seulement 3 paramètres :
- enableHighAccuracy : true/false, indique si l'on souhaite obtenir une position précise. Malheureusement, cela ne garanti pas un appel GPS sur tous les téléphones
- maximumAge : fraicheur maximale de la position en cache Ce critère n'est pas respecté par certains téléphones (IPhone en particulier)
- timeout : durée après laquelle la callback d'erreur est appelé si aucune position n'a été trouvé
S’il n’y a pas d’erreur une fonction callback de votre choix est appellée avec un paramètre contenant les informations suivantes :
- latitude, longitude : les coordonnées GPS en WGS84
- altitude (optionnel) : pour les alpinistes et aviateurs
- accuracy : très important, donne la précision estimée de la mesure. Heureusement cette valeur est "en général" assez honnête (comprenez proche de la vérité).
Avec cette fonction, vous ne pouvez donc pas être certain que la position obtenue soit précise, ni récente sur tous les terminaux. La position renvoyée peut être ancienne de plusieurs minutes et peut donc être très éloignée de votre position actuelle si vous venez de prendre un bus par exemple.
Vous pouvez peut-être vous en sortir avec plusieurs appels successifs à getCurrentPosition, mais c’est sans garantie et cela peut prendre du temps :
Un timeout trop court ne vous donnera pas de résultat, trop long, vous prenez le risque de faire attendre l'utilisateur
WatchPosition
L’autre fonction de l’API permet de suivre la position de l’internaute à intervalle régulier.
Les options sont les mêmes. La différence est le fait que la callback est appellé au bon vouloir de l'OS à chaque changement de position détecté par l’OS.
L’autre différence et avantage de taille c’est que vous pouvez interrompre le suivi de position par un appel à la fonction “clearwatch”.
Mes tests ont montrés que la première position obtenue (lors du premier appel à la fonction callback) était souvent mauvaise. Cette première réponse est obtenue très rapidement (ce qui renforce ma conviction que l’appareil sort ça du cache) alors que les suivantes prennent du temps à venir.
Pire, j’ai même constaté que parfois sur iOS la première position était mauvaise, mais l’accuracy bonne (65m sur un IPhone) : dans ce cas le téléphone vous ment.
Inversement les Android semblent plus honnêtes, mais ils ne donnent jamais une précision de moins de 500m si le GPS est désactivé (et vous n’avez aucun moyen de le savoir en JavaScript).
Pour comprendre ces comportements, j’ai construit une page de test et je me suis baladé dans Toulouse :

Lors du test ci dessus, j’ai récupéré successivement les positions 1, 2, 3 et 4. L’accuracy a diminué au fur et à mesure des résultats. On voit que la première position obtenue est très éloignée de ma position réelle : si j’avais donné les arrêts autour de moi avec cette position, le résultat aurait été très mauvais !
Construction de la méthode
Après ce qu’on a vu précédemment, on peut déduire les éléments suivants:
- il faut utiliser watchposition
- il ne faut pas faire confiance à la première position sauf si elle est inférieur à 65m (cas des desktop)
- dans tout les cas on doit abandonner après X secondes (disons 30 secondes) : si aucune position n'est donnée (timeout à X secondes) ou si les accuracy dépassent un certain seuil (166m dans mon cas)
Voici donc la stratégie que j’ai mis au point :

Le critère d’abandon de l’algorithme est donc représenté par la courbe bleue (limite temporelle). La courbe pointillée (limite de qualité), représente les valeurs de précision insuffisantes pour être prises en compte.
En résumé :
Après 30 secondes, si on a pas récupéré de position ou si on a récupéré des positions trop imprécises on abandonne.
Si à l’inverse on trouve une position en dessous du seuil limite rapidement, il peut être intéressant d'attendre encore un peu afin de voir si on peut faire encore mieux.
C’est l’idée de la courbe rouge : on diminue nos exigences au fur et à mesure que le temps passe.
Exemple : on a trouvé une position précise à 100 mètres au bout d’une seconde, alors :
- Cas 1 :on trouve une position précise à 50 mètres au bout de 2 secondes : ca valait le coup d'attendre, on s'arrete la et on affiche le résultat
- Cas 2 :on ne trouve pas de position plus précise au bout de 5 secondes : ce n'est pas la peine de faire attendre l'utilisateur plus longtemps, 100 mètres ce n'est pas si mal
Sur le graphique précédent, j’ai représenté un seuil d’acceptation linéaire, mais vous pouvez très bien choisir quelque chose de différent :

- par palier : très facile à coder : c'est ce que j'ai choisi
- fonction continue deux fois dérivable : pour les joueurs
- ... etc ...
La subtilité du code JavaScript est que comme vous ne pouvez pas savoir quand (et même si) watchPosition vous retournera une valeur. Donc vous devez lancer une tache de supervision à part avec un “setInterval”.
Pour voir la mise en pratique et le code rendez-vous sur la page de test. (Je commenterais plus le code d’ici quelques temps, promis :-)
Conclusion
La mise au point de cette méthode a pris beaucoup de temps, car si elle n’est pas complexe elle se base sur des observations de terrains longues et fastidieuses.
Une voie d’amélioration serait peut-être la prise en compte des user-agent pour une adaptation au terminal détecté.
Peut-être le ferais-je si j’en ai le temps et si je veux devenir célèbre remplir mon Github.
Maintenant les observations faites lors des tests m’amènent à penser que certains fournisseurs d’OS mobile dégradent volontairement l’expérience de la WebApp pour pousser l’utilisateur à préférer une application native. C’est particulièrement le cas pour l’une de ces compagnies (dont je tairais le nom, mais dont le logo est une pomme) qui dégrade un peu plus la qualité et la précision des coordonnées XY à chaque sortie de nouveau modèle.
Dans la bataille HTML5 vs application native, la précision JavaScript de géolocalisation pourrait être un bon indicateur de tendance.