Améliorer la précision GPS en JavaScript

géolocalisation JavaScript HTML5 depuis un mobile
Géolocalisation JavaScript
L'arrivée d'HTML5 a été accompagné de l'apparition de nouvelles API JavaScript, permettant notamment d'accéder aux éléments hardware des terminaux : Webcam, Micro, Batterie, ...

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 :
tableau des navigateurs supportant la géolocalisation Support de l'API par les navigateurs (source caniuse.com)


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.

4km !
L’écart entre la position réelle et celle renvoyée par un IPhone4 peut parfois dépasser 4000 mètres.


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 :

géolocalisation sur mobi.tisseo.fr

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

C’est cette fonction qui donne les plus mauvais résultats.

Elle dispose de seulement 3 paramètres :

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 :

var options = { timeout : 30000, enableHighAccuracy : true, maximumAge : 1000 }; navigator.geolocation.getCurrentPosition(showPosition, showError, options); function showPosition(position) { alert( "Latitude: " + position.coords.latitude + "Longitude: " + position.coords.longitude + "Accuracy: " + position.coords.accuracy ); } function showError(err) { alert("Error no : " + err.code); }

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”.

La confiance règne
Intuitivement, pour suivre une position on se dit que le navigateur ne pourra plus tricher : il doit appeler le GPS. Et pourtant ...


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 :

Résultat d'un test sur le terrain Résultat d'un test sur le terrain


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:

Voici donc la stratégie que j’ai mis au point :

Graphique d'acceptation de l'erreur de géolocalisation Critère d'arrêt ou d'abandon de l'algorithme


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 :

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 :

Graphique d'acceptation de l'erreur de géolocalisation Autres choix possibles de seuil d'acceptation


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.