правильный / лучший тип для хранения широты и долготы - PullRequest
61 голосов
/ 22 декабря 2008

В языке программирования системного уровня, таком как C, C ++ или D, каков наилучший тип / кодировка для хранения широты и долготы?

Опции, которые я вижу:

  • IEEE-754 FP в градусах или радианах
  • градусов или радиан, хранимых в виде значения с фиксированной точкой в ​​32 или 64-битном формате int
  • отображение целочисленного диапазона в диапазон градусов: -> deg = (360/2^32)*val
  • градусы, минуты, секунды и доли секунды, хранящиеся в виде битовых полей в int
  • структура какого-то рода.

У простого решения (FP) есть главная обратная сторона: он имеет крайне неоднородное разрешение (где-то в Англии он может измерять в микронах, а в Японии - нет). Также это имеет все проблемы сравнения FP и еще много чего. Другие варианты требуют дополнительных усилий в разных частях жизненного цикла данных. (генерация, презентация, расчеты и т. д.)

Одним интересным вариантом является тип с плавающей точностью, который, когда широта увеличивается, она получает больше битов, а долгота уменьшается (когда они сближаются в направлении полюсов).

Смежные вопросы, которые не совсем охватывают это:


Кстати: 32 бита дают разрешение E / W на экваторе около 0,3 дюйма. Это близко к шкале, на которой могут работать высококлассные GPS-установки (в некоторых режимах IIRC они могут снижаться до 0,5 в) .

OTOH, если 32 бита равномерно распределены по поверхности земли, вы можете индексировать квадраты размером около 344 м на стороне, 5 байтов дают 21 м, 6B-> 1,3 м и 8B-> 5 мм.

У меня сейчас нет особого смысла, но я работал с такими вещами раньше и ожидаю снова, в какой-то момент.

Ответы [ 12 ]

36 голосов
/ 22 декабря 2008

Самый простой способ - просто сохранить его как число с плавающей запятой / двойное число в градусах. Положительно для N и E, отрицательно для S и W. Просто помните, что минуты и секунды из 60 (таким образом, 31 45'N - это 31,75). Легко понять, что это за значения, посмотрев на них и, при необходимости, преобразование в радианы тривиально.

Расчеты по широте и долготе, такие как Великий круг расстояние между двумя координатами, в значительной степени зависят от тригонометрических функций, которые обычно используют двойные значения. Любой другой формат будет опираться как минимум на другую реализацию синуса, косинуса, atan2 и квадратного корня. Произвольные числа точности (например, BigDecimal в Java) не будут работать для этого. Что-то вроде int, где 2 ^ 32 распространяется равномерно, будет иметь похожие проблемы.

Точка единообразия возникла в нескольких комментариях. На этом я просто отмечу, что Земля по долготе не однородна. Долгота в одну угловую секунду у полярного круга меньше, чем у экватора. Поплавки двойной точности дают точность до миллиметра в любой точке Земли. Это не достаточно? Если нет, то почему?

Стоит также отметить, что вы хотите делать с этой информацией, поскольку требуемые типы вычислений будут влиять на то, какой формат хранения вы используете.

17 голосов
/ 22 декабря 2008

Долготы и широты обычно не известны с большей точностью, чем 32-разрядные числа с плавающей запятой. Так что если вы беспокоитесь о месте для хранения, вы можете использовать поплавки. Но в целом удобнее работать с числами как с двойными.

Радианы более удобны для теоретической математики. (Например, производная синуса является косинусом, только когда вы используете радианы.) Но градусы, как правило, более знакомы и их легче интерпретировать, поэтому вы можете придерживаться градусов.

10 голосов
/ 30 января 2012

Десятичное представление с точностью до 8 должно быть более чем достаточно согласно этой статье в Википедии о Десятичных градусах .

0 decimal places, 1.0 = 111 km
...
7 decimal places, 0.0000001 = 1.11 cm
8 decimal places, 0.00000001 = 1.11 mm
4 голосов
/ 22 декабря 2008

Могут ли проблемы, о которых вы упомянули со значениями с плавающей запятой, стать проблемой? Если ответ «нет», я бы предложил использовать значение радиан с двойной точностью - оно понадобится вам, если вы все равно будете выполнять тригонометрические вычисления.

Если при использовании двойных чисел может возникнуть проблема с потерей точности или вы не будете выполнять тригонометрию, я бы предложил ваше решение для отображения в целочисленный диапазон - это даст вам наилучшее разрешение и может быть легко преобразовано в какой бы формат отображения вы не использовали, он будет использоваться и - после выбора соответствующего 0-меридиана - может использоваться для преобразования в значения с плавающей запятой с высокой точностью.

PS: Я всегда задавался вопросом, почему, кажется, нет никого, кто использует геоцентрические сферические координаты - они должны быть достаточно близки к географическим координатам и не требуют всей этой причудливой математики на сфероидах делать вычисления; ради интереса я хотел преобразовать Gauss-Krüger-Koordinaten (которые используются немецким Katasteramt) в GPS-координаты - позвольте мне сказать, что это было некрасиво: один использует эллипсоид Бесселя, другой WGS84 и гаусс-Крюгер само отображение довольно безумно само по себе ...

3 голосов
/ 21 октября 2013

Какая кодировка «лучшая», действительно зависит от ваших целей / требований.

Если вы выполняете арифметику с широтой с плавающей точкой, долгота часто очень удобна. В других случаях декартовы координаты (то есть x, y, z) могут быть более удобными. Например, если вы заботитесь только о точках на поверхности земли, вы можете использовать n-вектор .

Что касается долговременного хранения, IEEE с плавающей запятой будет тратить биты для диапазонов, которые вам не нужны (для широт / долгот) или для точности, которые могут вас не беспокоить в случае декартовых координат (если вы не хотите очень хорошей точности) в происхождении по любой причине). Вы, конечно, можете отобразить любой тип координат в дюймах желаемого размера, так чтобы весь диапазон указанных целых чисел охватывал диапазон, который вас интересует, в разрешении, которое вас волнует.

Есть, конечно, другие вещи, о которых стоит подумать, а не просто не тратить биты в кодировке. Например, (Geohashes) [https://en.wikipedia.org/wiki/Geohash] обладают хорошим свойством, что легко найти другие geohashes в той же области. (У большинства будет тот же префикс, и вы можете вычислить префикс, который будут у других.) К сожалению, они сохраняют одинаковую точность в градусах долготы вблизи экватора и вблизи полюсов. В настоящее время я использую для хранения 64-битные геохэш, что дает разрешение около 3 м на экваторе.

Система Maidenhead Locator имеет некоторые сходные характеристики, но кажется более оптимизированной для обмена данными между людьми, а не для хранения на компьютере. (Хранение строк MLS приведет к потере большого количества битов для некоторого довольно тривиального обнаружения ошибок.)

Единственная система, которую я обнаружил, которая по-разному обрабатывает полюса, - это Система координат военной сетки , хотя она также кажется более ориентированной на человеческое общение. (И это похоже на боль от перевода в лат / долг.)

В зависимости от того, что именно вы хотите, вы можете использовать нечто похожее на Универсальную полярную сереографическую систему координат рядом с полюсами вместе с чем-то более вычислительным, чем UTM для остальной части и используйте не более одного бита, чтобы указать, какую из двух систем вы используете. Я говорю не более одного бита, потому что маловероятно, что большинство вопросов, которые вас интересуют, будут вблизи полюсов. Например, вы могли бы использовать «полубита», сказав, что 11 указывает на использование полярной системы, а 00, 01 и 10 указывают на использование другой системы и являются частью представления.

Извините, это немного долго, но я хотел сохранить то, что я недавно узнал. К сожалению, я не нашел ни одного стандартного, здравомыслящего и эффективного способа изобразить точку на земле с одинаковой точностью.

Редактировать: я нашел другой подход, который выглядит намного больше, чем вы хотели, так как он более непосредственно использует преимущества более низкой точности, необходимой для долготы ближе к полюсам. Оказывается, существует много исследований по сохранению нормальных векторов. Кодирование нормальных векторов с использованием оптимизированных сферических координат описывает такую ​​систему для кодирования нормальных векторов при сохранении минимального уровня точности, но ее также можно использовать для географических координат.

3 голосов
/ 22 декабря 2008
Разрешение

0,3 дюйма сводится к тому, что землетрясения в течение нескольких лет имеют значение. Возможно, вы захотите пересмотреть, почему вы считаете, что вам нужно такое точное разрешение по всему миру.

Некоторые распространяющиеся центры в Тихом океане изменяются на целых 15 см / год .

2 голосов
/ 19 января 2016

Java-программа для вычисления максимальной ошибки округления в метрах от приведения значений lat / long в 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);
  }
}

Выход:

Float max error (meters): 1.7791213425235692
Double max error (meters): 0.11119508289500799
2 голосов
/ 15 декабря 2015

http://www.esri.com/news/arcuser/0400/wdside.html
На экваторе дуга-секунда долготы приблизительно равна дуге-секунде широты, составляющей 1/60 морской мили (или 101,27 фута или 30,87 метра).

32-битная переменная содержит 23 явных бита данных.
180 * 3600 требуется log2 (648000) = 19,305634287546711769425914064259 бит данных. Обратите внимание, что знаковый бит хранится отдельно и, следовательно, нам нужно рассчитывать только на 180 градусов.
После вычитания из 23 битов для log2 (648000) у нас есть оставшиеся 3,69363612453288230574085935741 бит для субсекундных данных.
Это 2 ^ 3.694365712453288230574085935741 = 12,945382716049382716049382716053 частей в секунду.
Поэтому тип данных с плавающей запятой может иметь точность 30,87 / 12,945382716049382716049382716053 ~ = 2,38 метра на экваторе.

1 голос
/ 15 декабря 2018

Отличный вопрос!

Я знаю, что этому вопросу уже 9 лет, и я знаю только часть ответа, который вы искали, но я только что пришел сюда с похожим вопросом, и многие вещи изменились после того, как этот вопрос был задан, например, аппаратное обеспечение. и GPS доступны. Я часто работаю с этой темой в прошивках, связанных с различными типами GPS в различных приложениях, и потерял счет часов (и дней), которые я потратил на разработку «наилучшего дизайна» для различных приложений, с которыми я работал или развиты.

Как всегда, различные решения будут обеспечивать выгоды и затраты, и, в конечном счете, «лучший дизайн» всегда будет «наилучшим образом соответствовать» преимуществам и затратам в соответствии с требованиями системы. Вот некоторые вещи, которые я должен учитывать, когда задаю тот же вопрос:

Стоимость процессора

Если в CPU нет встроенного сопроцессора с плавающей запятой (как в случае многих микроконтроллеров), то работа с «float», «double» и «long double» может быть чрезвычайно дорогостоящей. Например, с одним 16-разрядным микроконтроллером, с которым я работаю регулярно, умножение с использованием «двойных» значений стоит 326 тактовых циклов ЦП, а деление - 1193 тактовых цикла. Очень дорого!

Точность компромисса

На экваторе - значение с плавающей запятой (32-разрядное значение IEEE-754 с плавающей запятой), необходимое для представления значения степени со знаком, предполагая, что 7 «чистых» значащих десятичных разрядов могут быть представлены, изменение одной наименьшей значащая десятичная цифра (например, от 179,9999 до 180,0000) будет представлять расстояние около 11,12 метра. Это может или не может соответствовать жестким требованиям к точности системы. Принимая во внимание, что «двойной» (с 15 представленными «чистыми» значащими десятичными цифрами, таким образом, изменение с 179,9999999999999 до 180,000000000000) составляет около 0,00011 мм.

Ограничения точности ввода

Если вы имеете дело с вводом с GPS, сколько цифр реальной точности вы получаете и сколько вам нужно сохранить?

Стоимость разработки

64-битное значение двойной точности IEEE-754 ('double') и 32-битное значение одинарной точности ('float') ОЧЕНЬ удобно использовать в языке C, так как математические библиотеки для обеих программ практически каждый компилятор C, и, как правило, очень надежны. Если ваш процессор оснащен аппаратным процессором с плавающей запятой, это простой выбор.

Оперативная память и хранение

Если вам необходимо хранить большое количество этих значений в ОЗУ (или в хранилище, например, MYSQL), доступное ОЗУ (и пространство для хранения) может повлиять на работоспособность решения.

Доступные данные против необходимых данных

Один из примеров, с которыми я имею дело при написании этой статьи (причина, по которой я пришел сюда на этот вопрос), - это то, что я имею дело с u-blox M8 GPS, который способен выдавать мне двоичную информацию GPS (сохраняя нагрузку на процессор перевод предложений ASCII NMEA). В этом двоичном формате (называемом «протоколом UBX») широта и долгота представлены в виде 32-разрядных целых чисел со знаком, представление которых может представлять точность (на экваторе) с точностью до 1,11 см. Например, долгота -105,0269805 градусов представлена ​​как -1050269805 (используется все 32 бита), а одно изменение LSb представляет собой изменение ширины на 1,11 см в любом месте и долготу на 1,11 см на экваторе (и меньше на более высоких широтах пропорционально косинусу). широты). Приложение, в котором используется GPS, выполняет навигационные задачи, для которых (уже существующий и хорошо протестированный код) требуются «двойные» типы данных. К сожалению, преобразование этого целого числа в 64-разрядный «двойной» IEEE-754 не может быть легко выполнено простым перемещением целых двоичных битов целого числа во внутренние биты представления «двойного», поскольку выполняемый десятичный сдвиг является десятичное смещение базы-10. Если бы вместо этого был десятичный сдвиг в base-2, тогда биты base-2 целого числа могли бы быть перемещены в битовые поля 'double' с очень небольшим требуемым переводом. Но, увы, это не относится к целому числу со знаком, которое у меня есть. Так что это будет стоить мне умножение на процессор, который не имеет аппаратного процессора с плавающей запятой: 326 тактов процессора.

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

Обратите внимание, что это умножение было выбрано для этого:

ldLatitude = (double)li32LatFromGps / 10000000.0;

потому что «двойное» умножение примерно в 3,6 раза быстрее, чем «двойное» деление на CPU, с которым я имею дело. Такова жизнь в мире микроконтроллеров. : -)

Что было бы BRILLIANT (и может быть в будущем, если я смогу сэкономить время на выходных), так это если бы задачи навигации можно было выполнять непосредственно с 32-разрядным целым числом со знаком! Тогда никакое преобразование не понадобится ... Но будет ли дороже выполнять задачи навигации с таким целым числом? Процессор стоит, наверное, гораздо эффективнее. Время разработки стоит? Это еще один вопрос, особенно с хорошо протестированной системой, которая использует 64-разрядные двойные значения IEEE-754! Кроме того, уже существует программное обеспечение, которое предоставляет картографические данные (с использованием «двойных» значений градусов), и это программное обеспечение также необходимо преобразовать для использования целого числа со знаком, а не задачи на одну ночь!

Один ОЧЕНЬ интересный вариант состоит в том, чтобы напрямую (без перевода) представлять пересечения между аппроксимациями «прямоугольников» (фактически трапеций, которые становятся треугольниками на полюсах), используя необработанные целые значения широты / долготы. На экваторе эти прямоугольники будут иметь размеры примерно 1,11 см восток-запад на 1,11 см север-юг, тогда как на широте, скажем, Лондон, Англия, размеры будут примерно 0,69 см восток-запад на 1,11 см север-юг. Это может или не может быть легко иметь дело, в зависимости от того, что нужно приложению.

В любом случае, я надеюсь, что эти мысли и обсуждения помогут другим, кто ищет в этой теме «лучший дизайн» для своей системы.

С уважением, Vic

1 голос
/ 22 февраля 2016

Следующий код упаковывает координаты WGS84 без потерь в беззнаковый длинный (т.е. в 8 байтов):

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);
        }
    }
}

Источник: http://www.dupuis.me/node/35

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...