web-dev-qa-db-fra.com

type correct / meilleur pour stocker la latitude et la longitude

Dans un langage de programmation de niveau système comme C, C++ ou D, quel est le meilleur type/encodage pour stocker la latitude et la longitude?

Les options que je vois sont:

  • IEEE-754 FP en degrés ou radians
  • degrés ou radians stockés sous forme de valeur à virgule fixe dans un int de 32 ou 64 bits
  • mappage d'une plage entière à la plage de degrés: -> deg = (360/2^32)*val
  • degrés, minutes, secondes et secondes fractionnaires stockés sous forme de champs de bits dans un int
  • une structure en quelque sorte.

La solution facile (FP) a l'inconvénient majeur d'avoir une résolution très non uniforme (quelque part en Angleterre, elle peut mesurer en microns, au Japon, elle ne peut pas). Cela présente également tous les problèmes de FP et ainsi de suite. Les autres options nécessitent des efforts supplémentaires dans différentes parties du cycle de vie des données. (Génération, présentation, calculs, etc.)

Une option intéressante est un type de précision flottant qui, lorsque la latitude augmente, obtient plus de bits et la longitude diminue (car ils se rapprochent vers les pôles).

Questions connexes qui ne couvrent pas tout à fait cela:


BTW: 32 bits vous donne une résolution E/W à l'équateur d'environ 0,3 pouce. C'est proche de l'échelle sur laquelle les configurations GPS de haute qualité peuvent fonctionner (IIRC elles peuvent descendre à environ 0,5 dans certains modes).

OTOH si les 32 bits sont uniformément répartis sur la surface de la terre, vous pouvez indexer des carrés d'environ 344m sur un côté, 5 octets donnent 21m, 6B-> 1,3m et 8B-> 5mm.

Je n'ai pas une utilisation spécifique en tête pour le moment, mais j'ai déjà travaillé avec ce genre de chose et je m'attends à le faire à nouveau à un moment donné.

63
BCS

Le moyen le plus simple consiste simplement à le stocker sous forme de flottant/double en degrés. Positif pour N et E, négatif pour S et W. N'oubliez pas que les minutes et les secondes sont sur 60 (donc 31 45'N est 31,75). Il est facile de comprendre quelles sont les valeurs en les regardant et, si nécessaire, la conversion en radians est triviale.

Les calculs de latitudes et de longitudes tels que la distance Grand Cercle entre deux coordonnées reposent fortement sur des fonctions trigonométriques, qui utilisent généralement des doubles. Tout autre format va s'appuyer sur une autre implémentation de sinus, cosinus, atan2 et racine carrée, au minimum. Les nombres de précision arbitraire (par exemple BigDecimal en Java) ne fonctionneront pas pour cela. Quelque chose comme l'int où 2 ^ 32 est réparti uniformément va avoir des problèmes similaires.

Le point d'uniformité a été soulevé dans plusieurs commentaires. Sur ce point, je noterai simplement que la Terre, en ce qui concerne la longitude, n'est pas uniforme. Une longitude d'arc-seconde au cercle polaire arctique est une distance plus courte qu'à l'équateur. Les flotteurs à double précision donnent une précision inférieure au millimètre partout sur Terre. N'est-ce pas suffisant? Sinon, pourquoi pas?

Il convient également de noter ce que vous souhaitez faire avec ces informations, car les types de calculs dont vous avez besoin auront un impact sur le format de stockage que vous utilisez.

37
cletus

Les longitudes et latitudes ne sont généralement pas connues avec une plus grande précision qu'un flotteur de 32 bits. Donc, si vous êtes préoccupé par l'espace de stockage, vous pouvez utiliser des flotteurs. Mais en général, il est plus pratique de travailler avec des nombres en double.

Les radians sont plus pratiques pour les mathématiques théoriques. (Par exemple, la dérivée du sinus n'est cosinus que lorsque vous utilisez des radians.) Mais les degrés sont généralement plus familiers et plus faciles à interpréter, vous pouvez donc vous en tenir aux degrés.

17
John D. Cook

Une représentation décimale avec une précision de 8 devrait être plus que suffisante selon cet article de wikipedia sur degrés décimaux .

0 decimal places, 1.0 = 111 km
...
7 decimal places, 0.0000001 = 1.11 cm
8 decimal places, 0.00000001 = 1.11 mm
10
Pykler

Les problèmes que vous avez mentionnés avec les valeurs à virgule flottante pourraient-ils devenir un problème? Si la réponse est non, je suggérerais simplement d'utiliser la valeur de radians en double précision - vous en aurez besoin si vous effectuez de toute façon des calculs trigonométriques.

S'il peut y avoir un problème de perte de précision lors de l'utilisation de doubles ou si vous ne faites pas de trigonométrie, je suggère votre solution de mappage sur une plage entière - cela vous donnera la meilleure résolution, peut facilement être convertie dans n'importe quel format d'affichage votre environnement local utilisera et - après avoir choisi un méridien 0 approprié - peut être utilisé pour convertir en valeurs à virgule flottante de haute précision.

PS: Je me suis toujours demandé pourquoi il ne semble y avoir personne qui utilise des coordonnées sphériques géocentriques - elles devraient être raisonnablement proches des coordonnées géographiques, et ne nécessiteront pas toutes ces mathématiques fantaisies sur les sphéroïdes à faire calculs; pour le plaisir, je voulais convertir Gauss-Krüger-Koordinaten (qui sont utilisés par le Katasteramt allemand) en coordonnées GPS - laissez-moi vous dire que c'était moche: l'un utilise l'ellipsoïde de Bessel, l'autre WGS84 et le Gauss-Krüger la cartographie elle-même est assez folle en soi ...

4
Christoph

Quel est le meilleur encodage dépend vraiment de vos objectifs/exigences.

Si vous effectuez une latitude arithmétique à virgule flottante, la longitude est souvent très pratique. D'autres fois, les coordonnées cartésiennes (c'est-à-dire x, y, z) peuvent être plus pratiques. Par exemple, si vous ne vous souciez que des points à la surface de la terre, vous pouvez utiliser un n-vector .

En ce qui concerne le stockage à plus long terme, la virgule flottante IEEE gaspille des bits pour des plages qui ne vous intéressent pas (pour lat/lon) ou pour une précision dont vous ne vous souciez pas dans le cas des coordonnées cartésiennes (sauf si vous voulez une très bonne précision à l'origine pour quelque raison que ce soit). Vous pouvez bien sûr mapper l'un ou l'autre type de coordonnées en pouces de votre taille préférée, de sorte que toute la gamme desdits pouces couvre la plage qui vous intéresse à la résolution qui vous intéresse.

Il y a bien sûr d'autres choses à penser que de ne pas simplement gaspiller des bits dans le codage. Par exemple, (Geohashes) [https://en.wikipedia.org/wiki/Geohash] ont la propriété Nice qu'il est facile de trouver d'autres geohashes dans la même zone. (La plupart auront le même préfixe, et vous pouvez calculer le préfixe que les autres auront.) Malheureusement, ils conservent la même précision en degrés longitude près de l'équateur que près des pôles. J'utilise actuellement des géohashs 64 bits pour le stockage, ce qui donne une résolution d'environ 3 m à l'équateur.

Le Maidenhead Locator System a des caractéristiques similaires, mais semble plus optimisé pour communiquer des emplacements entre humains plutôt que de les stocker sur un ordinateur. (Le stockage de chaînes MLS gaspillerait beaucoup de bits pour une détection d'erreur plutôt triviale.)

Le seul système que j'ai trouvé qui gère les pôles différemment est le Military Grid Reference System , bien qu'il semble également plus orienté vers les communications humaines. (Et il semble difficile de se convertir de ou vers lat/lon.)

Selon ce que vous voulez exactement, vous pouvez utiliser quelque chose de similaire au Système de coordonnées séréographiques polaires universel près des pôles avec quelque chose de plus sain d'ordinaire que UTM pour le reste du monde, et utilisez au plus un bit pour indiquer lequel des deux systèmes vous utilisez. Je dis tout au plus un peu, car il est peu probable que la plupart des points qui vous intéressent soient près des pôles. Par exemple, vous pouvez utiliser "un demi-bit" en disant que 11 indique l'utilisation du système polaire, tandis que 00, 01 et 10 indiquent l'utilisation de l'autre système et font partie de la représentation.

Désolé, c'est un peu long, mais je voulais sauvegarder ce que j'avais appris récemment. Malheureusement, je n'ai trouvé aucun moyen standard, sain et efficace de représenter un point sur terre avec une précision uniforme.

Edit: j'ai trouvé une autre approche qui ressemble beaucoup plus à ce que vous vouliez, car elle tire plus directement parti de la précision plus faible nécessaire pour une longitude plus proche des pôles. Il s'avère qu'il y a beaucoup de recherches sur le stockage de vecteurs normaux. Encodage de vecteurs normaux en utilisant des coordonnées sphériques optimisées décrit un tel système pour coder des vecteurs normaux tout en maintenant un niveau de précision minimum, mais il pourrait tout aussi bien être utilisé pour des coordonnées géographiques.

3
aij

A Java pour calculer l'erreur d'arrondi max en mètres à partir de la conversion des valeurs lat/long en Float/Double:

import Java.util.*;
import Java.lang.*;
import com.javadocmd.simplelatlng.*;
import com.javadocmd.simplelatlng.util.*;

public class MaxError {
  public static void main(String[] args) {
    Float flng = 180f;
    Float flat = 0f;
    LatLng fpos = new LatLng(flat, flng);
    double flatprime = Float.intBitsToFloat(Float.floatToIntBits(flat) ^ 1);
    double flngprime = Float.intBitsToFloat(Float.floatToIntBits(flng) ^ 1);
    LatLng fposprime = new LatLng(flatprime, flngprime);

    double fdistanceM = LatLngTool.distance(fpos, fposprime, LengthUnit.METER);
    System.out.println("Float max error (meters): " + fdistanceM);

    Double dlng = 180d;
    Double dlat = 0d;
    LatLng dpos = new LatLng(dlat, dlng);
    double dlatprime = Double.longBitsToDouble(Double.doubleToLongBits(dlat) ^ 1);
    double dlngprime = Double.longBitsToDouble(Double.doubleToLongBits(dlng) ^ 1);
    LatLng dposprime = new LatLng(dlatprime, dlngprime);

    double ddistanceM = LatLngTool.distance(dpos, dposprime, LengthUnit.METER);
    System.out.println("Double max error (meters): " + ddistanceM);
  }
}

Production:

Float max error (meters): 1.7791213425235692
Double max error (meters): 0.11119508289500799
3
John W. Phillips

Une résolution de 0,3 pouce descend au point où les tremblements de terre sur quelques années font une différence. Vous voudrez peut-être reconsidérer pourquoi vous pensez avoir besoin d'une résolution aussi fine dans le monde entier.

Certains des centres d'épandage dans l'océan Pacifique changent jusqu'à 15 cm/an .

3
Greg Hewgill

http://www.esri.com/news/arcuser/0400/wdside.html
À l'équateur, une seconde d'arc de longitude équivaut approximativement à une seconde d'arc de latitude, ce qui correspond à 1/60e de mille marin (ou 101,27 pieds ou 30,87 mètres).

Le flottant 32 bits contient 23 bits explicites de données.
180 * 3600 nécessite log2 (648000) = 19,305634287546711769425914064259 bits de données. Notez que le bit de signe est stocké séparément et que nous devons donc n'atteindre que 180 degrés.
Après avoir soustrait de 23 les bits pour log2 (648000), il nous reste 3,6694365712453288230574085935741 bits supplémentaires pour les données d'une seconde.
Soit 2 ^ 3.694365712453288230574085935741 = 12.945382716049382716049382716053 parties par seconde.
Par conséquent, un type de données flottant peut avoir une précision de 30,87/12,945382716049382716049382716053 ~ = 2,38 mètres à l'équateur.

3
Roland Pihlakas

Grande question!

Je sais que cette question a maintenant 9 ans, et je ne connais qu'une partie de la réponse que vous cherchiez, mais je suis juste venu ici avec une question similaire, et beaucoup de choses ont changé depuis que cette question a été posée, comme le matériel et les GPS disponibles . Je travaille fréquemment avec ce sujet dans des microprogrammes traitant de différents types de GPS dans différents types d'applications, et j'ai perdu le nombre d'heures (et de jours) que j'ai passé à élaborer "la meilleure conception" pour différentes applications avec lesquelles j'ai travaillé ou développé.

Comme toujours, différentes solutions vont offrir des avantages et des coûts et, en fin de compte, une "meilleure conception" sera toujours une "meilleure adéquation" des avantages et des coûts avec les exigences du système. Voici quelques éléments que je dois considérer lorsque je pose la même question:

Coût du temps CPU

Si le processeur n'a pas de coprocesseur à virgule flottante intégré (comme c'est le cas avec de nombreux microcontrôleurs), alors traiter avec 'float', 'double' et 'long double' peut être extrêmement coûteux. Par exemple, avec un microcontrôleur 16 bits avec lequel je travaille régulièrement, une multiplication utilisant des valeurs "doubles" coûte 326 cycles d'horloge CPU et une division coûte 1193 cycles d'horloge. Très cher!

Compromis de précision

À l'équateur, un "flottant" (valeur à virgule flottante 32 bits IEEE-754), devant représenter une valeur de degré signée, en supposant que 7 chiffres décimaux significatifs "propres" peuvent être représentés, le changement d'un chiffre décimal le moins significatif (par exemple de 179,9999 à 180,0000) va représenter une distance d'environ 11,12 mètres. Cela peut ou non répondre aux exigences strictes de précision du système. Alors qu'un "double" (avec 15 chiffres décimaux significatifs "propres" représentés, donc un changement de 179,999999999999 à 180,000000000000) représente environ 0,00011 mm.

Limites de précision d'entrée

Si vous avez affaire à la saisie d'un GPS, combien de chiffres de précision réelle obtenez-vous et combien devez-vous conserver?

Coûts de temps de développement

Une valeur de double précision 64 bits ("double") et une valeur de simple précision 32 bits ("float") IEEE-754 sont TRÈS pratiques à gérer dans le langage C car les bibliothèques mathématiques pour les deux sont fournies avec pratiquement tous les compilateurs C et sont généralement très fiables. Si votre CPU est livré avec un processeur matériel à virgule flottante, c'est un choix facile.

RAM et coûts de stockage

Si vous devez conserver un grand nombre de ces valeurs dans RAM (ou dans le stockage, par exemple MYSQL), la RAM disponible (et l'espace de stockage) peut avoir un impact sur la faisabilité de la solution.

Données disponibles vs données requises

Un exemple que je traite à la présente écriture (la raison pour laquelle je suis venu ici à cette question) est que je traite avec un u-blox M8 GPS qui est capable de me donner des informations GPS binaires (en économisant la surcharge CPU de la traduction ASCII phrases NMEA). Dans ce format binaire (appelé "protocole UBX"), la latitude et la longitude sont représentées sous la forme d'entiers signés de 32 bits, cette représentation pouvant représenter une précision (à l'équateur) de 1,11 cm environ. Par exemple, -105,0269805 degrés de longitude est représenté par -1050269805 (en utilisant les 32 bits) et un changement LSb représente environ 1,11 cm de changement de latitude n'importe où, et 1,11 cm de longitude à l'équateur (et moins aux latitudes plus élevées, proportionnellement au cosinus). de la latitude). L'application dans laquelle se trouve ce GPS effectue des tâches de navigation qui (code déjà existant et bien testé) nécessitent des types de données "doubles". Malheureusement, la conversion de cet entier en un "double" 64 bits IEEE-754 ne peut pas être facilement effectuée simplement en déplaçant les bits de base 2 de l'entier dans les bits de représentation interne du "double" car le décalage décimal à effectuer est un décalage décimal en base 10. S'il s'agissait plutôt d'un décalage décimal en base 2, les bits de base 2 de l'entier pourraient être déplacés dans les champs binaires du "double" avec très peu de traduction requise. Mais hélas, ce n'est pas le cas avec l'entier signé que j'ai. Cela va donc me coûter une multiplication sur un CPU qui n'a pas de processeur matériel à virgule flottante: 326 cycles d'horloge CPU.

double   ldLatitude;
int32_t  li32LatFromGps;
ldLatitude = (double)li32LatFromGps * 0.0000001;

Notez que cette multiplication a été choisie par dessus:

ldLatitude = (double)li32LatFromGps / 10000000.0;

parce que la multiplication "double" est environ 3,6 fois plus rapide que la division "double" sur le processeur auquel je fais face. Telle est la vie dans le monde des microcontrôleurs. :-)

Ce qui aurait été BRILLANT (et pourrait l'être à l'avenir si je peux épargner du temps le week-end), c'est si les tâches de navigation pouvaient être effectuées directement avec l'entier signé 32 bits! Aucune conversion ne serait alors nécessaire ... Mais cela coûterait-il plus cher de faire les tâches de navigation avec un tel entier? Coûts du processeur, probablement beaucoup plus efficaces. Coûts de temps de développement? C'est une autre question, en particulier avec un système bien testé déjà en place, qui utilise des valeurs "double" 64 bits IEEE-754! De plus, il existe un logiciel déjà existant qui fournit des données cartographiques (en utilisant des valeurs de degré "double"), lequel logiciel devrait être converti pour utiliser également l'entier signé - pas une tâche du jour au lendemain!

Une option TRÈS intéressante est de représenter directement (sans traduction) les intersections entre les approximations de "rectangles" (en fait des trapèzes, qui deviennent des triangles aux pôles) en utilisant les entiers bruts de latitude/longitude. À l'équateur, ces rectangles auraient des dimensions d'environ 1,11 cm est-ouest par 1,11 cm nord-sud, tandis qu'à une latitude de Londres, par exemple, les dimensions seraient d'environ 0,69 cm est-ouest par 1,11 cm nord-sud. Cela peut ou non être facile à gérer, selon les besoins de l'application.

Quoi qu'il en soit, j'espère que ces réflexions et discussions aideront les autres qui envisagent ce sujet pour "la meilleure conception" pour leur système.

Cordialement, Vic

1
V. Wheeler

Le code suivant regroupe les coordonnées WGS84 sans perte dans un long non signé (c'est-à-dire en 8 octets):

using System;
using System.Collections.Generic;
using System.Text;

namespace Utils
{
    /// <summary>
    /// Lossless conversion of OSM coordinates to a simple long.
    /// </summary>
    unsafe class CoordinateStore
    {
        private readonly double _lat, _lon;
        private readonly long _encoded;

        public CoordinateStore(double lon,double lat)
        {
            // Ensure valid lat/lon
            if (lon < -180.0) lon = 180.0+(lon+180.0); else if (lon > 180.0) lon = -180.0 + (lon-180.0);
            if (lat < -90.0) lat = 90.0 + (lat + 90.0); else if (lat > 90.0) lat = -90.0 + (lat - 90.0);

            _lon = lon; _lat = lat;

            // Move to 0..(180/90)
            var dlon = (decimal)lon + 180m;
            var dlat = (decimal)lat + 90m;

            // Calculate grid
            var grid = (((int)dlat) * 360) + ((int)dlon);

            // Get local offset
            var ilon = (uint)((dlon - (int)(dlon))*10000000m);
            var ilat = (uint)((dlat - (int)(dlat))*10000000m);

            var encoded = new byte[8];
            fixed (byte* pEncoded = &encoded[0])
            {
                ((ushort*)pEncoded)[0] = (ushort) grid;
                ((ushort*)pEncoded)[1] = (ushort)(ilon&0xFFFF);
                ((ushort*)pEncoded)[2] = (ushort)(ilat&0xFFFF);
                pEncoded[6] = (byte)((ilon >> 16)&0xFF);
                pEncoded[7] = (byte)((ilat >> 16)&0xFF);

                _encoded = ((long*) pEncoded)[0];
            }
        }

        public CoordinateStore(long source)
        {
            // Extract grid and local offset
            int grid;
            decimal ilon, ilat;
            var encoded = new byte[8];
            fixed(byte *pEncoded = &encoded[0])
            {
                ((long*) pEncoded)[0] = source;
                grid = ((ushort*) pEncoded)[0];
                ilon = ((ushort*)pEncoded)[1] + (((uint)pEncoded[6]) << 16);
                ilat = ((ushort*)pEncoded)[2] + (((uint)pEncoded[7]) << 16);
            }

            // Recalculate 0..(180/90) coordinates
            var dlon = (uint)(grid % 360) + (ilon / 10000000m);
            var dlat = (uint)(grid / 360) + (ilat / 10000000m);

            // Returns to WGS84
            _lon = (double)(dlon - 180m);
            _lat = (double)(dlat - 90m);
        }

        public double Lon { get { return _lon; } }
        public double Lat { get { return _lat; } }
        public long   Encoded { get { return _encoded; } }


        public static long PackCoord(double lon,double lat)
        {
            return (new CoordinateStore(lon, lat)).Encoded;
        }
        public static KeyValuePair<double, double> UnPackCoord(long coord)
        {
            var tmp = new CoordinateStore(coord);
            return new KeyValuePair<double, double>(tmp.Lat,tmp.Lon);
        }
    }
}

Source: http://www.dupuis.me/node/35

1
Augustin

Si en "stockant" vous voulez dire "garder en mémoire", la vraie question est: qu'allez-vous en faire?

Je soupçonne qu'avant que ces coordonnées ne fassent quelque chose d'intéressant, elles auront été canalisées en radians à travers les fonctions de math.h. Sauf si vous prévoyez d'implémenter un certain nombre de fonctions transcendantales qui fonctionnent sur Deg/Min/Secs regroupées dans un champ de bits.

Alors pourquoi ne pas garder les choses simples et les stocker simplement en degrés IEEE-754 ou en radians à la précision de vos besoins?

1
natevw

Vous pouvez utiliser le type de données decimal:

CREATE TABLE IF NOT EXISTS `map` (
  `latitude` decimal(18,15) DEFAULT NULL,
  `longitude` decimal(18,15) DEFAULT NULL 
);
0
Lemon Kazi