如何计算 GeoDjango 中两点之间的 3D 距离(包括高度)

How to calculate 3D distance (including altitude) between two points in GeoDjango

序言:

这是SO中经常出现的问题:

我想编写一个关于 SO 文档的示例,但是 geodjango 章节从未起飞,并且由于文档于 2017 年 8 月 8 日关闭,我将遵循 this widely upvoted and discussed meta answer 的建议并且将我的例子写成自我回答 post.

当然,我也很乐意看到任何不同的方法!!


问题:

假设模型:

class MyModel(models.Model):
    name = models.CharField()
    coordinates = models.PointField()

我将 coordinate 变量中的点存储为 lan, lng, alt 点:

MyModel.objects.create(
    name='point_name', 
    coordinates='SRID=3857;POINT Z (100.00 10.00 150)')

我正在尝试计算两个这样的点之间的 3D 距离:

p1 = MyModel.objects.get(name='point_1').coordinates
p2 = MyModel.objects.get(name='point_2').coordinates

d = Distance(m=p1.distance(p2))

现在 d=X 以米为单位。

如果我只改变其中一个点的高度:

例如:

p1.coordinates = 'SRID=3857;POINT Z (100.00 10.00 200)'

由之前的150计算:

d = Distance(m=p1.distance(p2))
又是

returnsd=X,好像忽略了高程。
如何计算点之间的 3D 距离?

阅读有关 GEOSGeometry.distance 方法的文档:

Returns the distance between the closest points on this geometry and the given geom (another GEOSGeometry object).

Note

GEOS distance calculations are linear – in other words, GEOS does not perform a spherical calculation even if the SRID specifies a geographic coordinate system.

因此我们需要实现一种方法来计算两点之间更准确的二维距离,然后我们可以尝试应用这些点之间的高度 (Z) 差异。

1.大圆二维距离计算:

计算球体表面两点之间距离的最常用方法(因为地球是简单但通常建模的)是 Haversine formula:

The haversine formula determines the great-circle distance between two points on a sphere given their longitudes and latitudes.

尽管从 great-circle distance wiki page 我们读到:

Although this formula is accurate for most distances on a sphere, it too suffers from rounding errors for the special (and somewhat unusual) case of antipodal points (on opposite ends of the sphere). A formula that is accurate for all distances is the following special case of the Vincenty formula for an ellipsoid with equal major and minor axes.

我们可以创建我们自己的 Haversine 或 Vincenty 公式的实现(如此处所示的 Haversine:Haversine Formula in Python (Bearing and Distance between two GPS points)) or we can use one of the already implemented methods contained in geopy:

  • geopy.distance.great_circle (半正矢):

        from geopy.distance import great_circle
        newport_ri = (41.49008, -71.312796)
        cleveland_oh = (41.499498, -81.695391)
    
        # This call will result in 536.997990696 miles
        great_circle(newport_ri, cleveland_oh).miles) 
    
  • geopy.distance.vincenty (文森蒂):

        from geopy.distance import vincenty
        newport_ri = (41.49008, -71.312796)
        cleveland_oh = (41.499498, -81.695391)
    
        # This call will result in 536.997990696 miles
        vincenty(newport_ri, cleveland_oh).miles
    

2。在混合中添加高度:

如前所述,上述每个计算都会产生两点之间的大圆距离。该距离也称为“乌鸦飞行”,假设“乌鸦”在不改变高度的情况下飞行,并且尽可能笔直地从点 A 飞到点 B。

我们可以更好地估计“walking/driving”(“as the crow walks”??)距离,方法是将之前方法之一的结果与两者之间的高度差(delta)相结合A点和B点,在Euclidean Formula内进行距离计算:

acw_dist = sqrt(great_circle(p1, p2).m**2 + (p1.z - p2.z)**2)

以前的解决方案容易出错,尤其是点之间的实际距离越长。
我把它留在这里是为了继续评论。

GeoDjango Distance 计算两点之间的二维距离,不考虑高度差。
为了获得 3D 计算,我们需要创建一个距离函数,在计算中考虑高度差异:

理论:

latitudelongitudealtitude极坐标,我们需要将它们转换为直角坐标(x, y, z) 以便对它们应用 Euclidean Formula 并计算它们的 3D 距离。

  • 假设:
    polar_point_1 = (long_1, lat_1, alt_1)

    polar_point_2 = (long_2, lat_2, alt_2)

  • 使用以下公式将每个点转换为笛卡尔等效项:

     x = alt * cos(lat) * sin(long)
     y = alt * sin(lat)
     z = alt * cos(lat) * cos(long)
    

你将分别获得 p_1 = (x_1, y_1, z_1)p_2 = (x_2, y_2, z_2) 分。

  • 终于用到了欧氏公式:

     dist = sqrt((x_2-x_1)**2 + (y_2-y_1)**2 + (z_2-z_1)**2)
    

转换为笛卡尔坐标后,您可以使用 numpy 计算范数:

np.linalg.norm(point_1 - point_2)

使用geopy,这是最简单完美的解决方案。

https://geopy.readthedocs.io/en/stable/#geopy.distance.lonlat

>>> from geopy.distance import distance
>>> from geopy.point import Point
>>> a = Point(-71.312796, 41.49008, 0)
>>> b = Point(-81.695391, 41.499498, 0)
>>> print(distance(a, b).miles)
538.3904453677203