Внедрение географического координатного класса: сравнение равенства
Я интегрирую географический координатный класс из CodePlex в свою личную библиотеку инструментов. Этот класс использует поля float
для хранения широты и долготы.
Так как класс GeoCoordinate
реализует IEquatable<GeoCoordinate>
, я обычно написал метод Equals
следующим образом:
public bool Equals(GeoCoordinate other)
{
if (other == null) {
return false;
}
return this.latitude == other.latitude && this.longitude == other.longitude;
}
В этот момент я остановился и подумал, что я сравниваю переменные с плавающей запятой для равенства, что обычно не-нет. Мой мыслительный процесс затем пошел примерно следующим образом:
-
Я могу только представить настройки свойств Latitude
и Longitude
один раз, что означает, что не будет ошибок, накопленных, чтобы испортить мои сравнения.
-
С другой стороны, возможно (хотя и бессмысленно) писать
var geo1 = new GeoCoordinate(1.2, 1.2);
var geo2 = new GeoCoordinate(1.2, 1.2);
// geo1.Equals(geo2) will definitely be true, BUT:
geo2.Latitude *= 10;
geo2.Latitude /= 10;
// I would think that now all bets are off
Конечно, это не то, что я могу себе представить, но если открытый интерфейс класса позволяет это, то Equals
должен иметь возможность обрабатывать его.
-
Сравнение для равенства с использованием теста difference < epsilon
позволило бы решить проблему сравнения двух экземпляров, но создать больше проблем:
- Как сделать переходным? Это звучит невозможно.
-
Как создать тот же хэш-код для всех значений, которые сравнивались бы равными?
Скажем, что epsilon = 0.11
(случайный пример). Из этого следует, что GeoCoordinate { 1, 1 }
нужен тот же хэш-код, что и GeoCoordinate { 1.1, 1.1 }
. Но последнему нужен тот же хэш-код, что и GeoCoordinate { 1.2, 1.2 }
. Вы можете видеть, где это происходит: все экземпляры должны иметь один и тот же хэш-код.
-
Решение всего этого заключалось бы в том, чтобы сделать GeoCoordinate
неизменяемым классом. Это также решило бы проблему GetHashCode
: она основана на широте и долготе (что еще), и если они являются изменяемыми, то использование GeoCoordinate
в качестве ключа в словаре требует проблем. Однако для того, чтобы класс неизменяемый имел свои недостатки:
- Вы не можете создавать экземпляры экземпляра класса (парадигма WPF), которые могут быть причиной в некоторых случаях
- Сериализация, вероятно, также станет болью из-за потери конструктора без параметров (я не эксперт по сериализации .NET, так что подробно, как я вижу здесь)
Какой подход вы бы предложили? Легко сделать класс соответствующим требованиям, которые я имею прямо сейчас (просто сделайте его неизменным), но есть ли лучший способ?
Изменить. Я добавил элемент 3 в списке выше, переместив предыдущий элемент 3 в позицию 4.
Решение
Я собираюсь дать еще немного времени для обратной связи, но в настоящее время я иду с неизменным подходом. struct
(потому что это то, что сейчас) с соответствующими членами, можно увидеть здесь; комментарии и предложения более чем приветствуются.
Ответы
Ответ 1
"Место" с долготой/широтой меня довольно хорошо вписывается в слот "неизменяемого значения". Сама позиция не меняется - если вы измените широту, которая является другой позицией. Оттуда это может быть struct
; для float
в любом случае struct
будет иметь тот же размер, что и x64-ссылка, поэтому нет реальной нижней стороны.
Re равенство; если позиция не совсем то же самое, она не "равна", по крайней мере, в "ключевой" перспективе, поэтому я был бы счастлив с ==
здесь. Вы можете добавить метод "находится внутри (x)", если это поможет. Разумеется, геометрия большой дуги также не является абсолютно свободной: p
Мысли:
- он должен переопределить
bool Equals(object)
, а также добавить bool Equals(GeoCoordinate)
- он должен переопределить
GetHashCode()
и реализовать IEquatable<GeoCoordinate>
- статические операторы являются приятными для использования дополнительными
Ответ 2
-
Equals
в основном используется в словарях, поэтому руководство, которое вы должны сравнивать поплавками только с epsilon, здесь не применяется. Не пытайтесь вставить epsilon logic в Equals
. Это задача пользователей делать это по мере необходимости. Относительно легко доказать, что единственная хеш-функция, согласующаяся с epsilon, сравнивает ее с постоянной хэш-функцией.
-
У вашей реализации есть одна проблема: она не обрабатывает NaN
s. Вы должны использовать Equals
вместо ==
для отдельных координат для вашего метода Equals
.
public bool Equals(GeoCoordinate other)
{
if (other == null) {
return false;
}
return this.latitude.Equals( other.latitude) && this.longitude.Equals(other.longitude);
}
-
"Не сравнивать с помощью ==
, но с использованием epsilon" для кода потребления, а не для кода реализации. Поэтому я бы выполнил функцию, которая возвращает расстояние между двумя гео-координатами и сообщит пользователю использовать это для его сравнений с epsilon.
-
Я определенно сделаю его неизменным (не уверен, что структура или класс). Он имеет семантику значений и, следовательно, должен быть неизменным.
Обычно я использую что-то вроде Linq-to-Xml
/Linq-to-Json
для сериализации, поскольку это позволяет мне преобразовать представление между моей моделью в памяти и моделью на диске.
Но вы правы, что многие сериализаторы не поддерживают конструкторы по умолчанию. Я считаю это большой ошибкой в этих сериализаторах, а не как недостаток в моей модели. Некоторые сериализаторы просто получают доступ к частным сеттерам/полям, но лично я думаю, что воняет.
Ответ 3
Я бы использовал длинные целые числа вместо чисел с плавающей запятой в базовой модели широты и долготы. Миллисекунда градуса в любом случае меньше двух дюймов, что должно быть достаточно деталей: хранить ваши координаты в миллисекундах, и это проще, чище и безошибочно.
Ответ 4
В зависимости от использования структуры lat/lon я буду обеспокоен использованием float вместо double для lat/lon. Например, если вы выполняете интеграцию lat/lon в реальном времени, вам потребуется двойная точность. Это связано с тем, что степень составляет 1 морскую милю, и в зависимости от временного шага интеграции вы перемещаете очень маленькую сумму за итерацию времени.
Для выполнения теста равенства я бы использовал формулу простого расстояния, основанную на равноугольной аппроксимации, и решил, что если бы другая точка находилась в пределах моей разрешенной толерантности (дюйм или два), тогда объявите их равными. Скорректированное эквипотенциальное приближение дается здесь.