实现地理坐标类:相等比较
我正在将CodePlex的地理坐标类集成到我的个人“工具箱”库中。 此类使用float
字段来存储纬度和经度。
由于GeoCoordinate
类实现了IEquatable
,我习惯性地编写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
比较difference < epsilon
test将解决比较两个实例的问题,但会产生更多问题:- 如何使平等传递? 听起来不可能。
-
如何为比较相等的所有值生成相同的哈希码?
假设
epsilon = 0.11
(随机例子)。 因此,GeoCoordinate { 1, 1 }
需要与GeoCoordinate { 1.1, 1.1 }
GeoCoordinate { 1, 1 }
相同的哈希码。 但后者需要与GeoCoordinate { 1.2, 1.2 }
相同的哈希码。 您可以看到它的发展方向: 所有实例都需要具有相同的哈希码 。
-
所有这一切的解决方案是使
GeoCoordinate
成为一个不可变的类。 这也将解决GetHashCode
问题:它基于纬度和经度(还有其他),如果这些是可变的,那么使用GeoCoordinate
作为字典的关键是要求麻烦。 但是,使类不可变有其自身的缺点:- 你不能实例化和配置类的实例(WPF范例),这在某些情况下可能会很痛苦
- 由于失去了无参数构造函数,序列化可能也会变得很痛苦(我不是.NET序列化专家,所以我在这里看到的细节很多)
你会建议采用哪种方法? 很容易让这个类符合我现在的要求(只是让它变得不可变),但是有更好的方法吗?
编辑 :我在上面的列表中添加了一个项目3,将前一个项目3移动到位置4。
解
我将允许更多的时间来反馈,但现在我采用不可变的方法。 可以在这里看到与相关成员的struct
(因为它就是现在的结果); 评论和建议超过欢迎。
对我来说经度/纬度的“位置”非常适合“不可变值”槽。 位置本身不会改变 – 如果您更改不同位置的纬度。 从那里,它可以是一个struct
; 对于float
, struct
的大小与x64引用的大小相同,所以没有真正的缺点。
重新平等; 如果位置不完全相同,它不是“等于”,至少在“关键”视角,所以我对这里的==
感到满意。 如果它有帮助,您可以添加“在(x)距离内”方法。 当然,大弧几何也不是完全自由的; p
但是思考:
- 它应该覆盖
bool Equals(object)
以及添加bool Equals(GeoCoordinate)
- 它应该重写
GetHashCode()
并实现IEquatable
- 静态运算符是一个很好的可选附加function
-
Equals
主要用于字典,因此您应该比较的指南仅与epsilon一起使用不适用于此处。 不要试图将epsilon逻辑置于Equals
。 根据需要,这是用户的工作。 certificate与epsilon一致的唯一哈希函数与常量哈希函数进行比较相对容易。 -
您的实现有一个问题:它不处理
NaN
。 您需要在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
的序列化,因为这允许我在内存模型和磁盘模型之间转换表示。
但你是对的,许多序列化程序不支持非默认构造函数。 我认为这是那些序列化程序中的一个大缺陷,而不是我模型中的一个缺陷。 一些序列化程序只是访问私有的setter /字段,但我个人认为这很糟糕。
我会在纬度和经度的基础模型中使用长整数而不是浮点数。 在任何情况下,毫秒的度数都小于2英寸,这应该足够详细:以毫秒为单位存储坐标,它更简单,更清晰,更防错。
根据你对lat / lon结构的使用,我会担心使用浮点数代替lat / lon的双精度数。 例如,如果您正在进行lat / lon的实时集成,那么您将需要双精度。 这是因为度数是1海里,并且取决于积分的时间步长,每次迭代只移动非常小的量。
为了进行相等测试,我将使用基于等距矩形近似的简单距离公式,并确定如果另一个点在我的确定公差范围内(一英寸或两英寸),则声明它们相等。 这里给出了我将使用的等距矩形近似。