Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
652 views
in Technique[技术] by (71.8m points)

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

Prologue:

This is a question arising often in SO:

I wanted to compose an example on SO Documentation but the geodjango chapter never took off and since the Documentation got shut down on August 8, 2017, I will follow the suggestion of this widely upvoted and discussed meta answer and write my example as a self-answered post.

Of course, I would be more than happy to see any different approach as well!!


Question:

Assume the model:

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

Where I store the point in the coordinate variable as a lan, lng, alt point:

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

I am trying to calculate the 3D distance between two such points:

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

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

Now d=X in meters.

If I change only the altitude of one of the points in question:

For example:

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

from 150 previously, the calculation:

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

returns d=X again, like the elevation is ignored.
How can I calculate the 3D distance between my points?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Reading from the documentation on the GEOSGeometry.distance method:

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.

Therefore we need to implement a method to calculate a more accurate 2D distance between 2 points and then we can try to apply the altitude (Z) difference between those points.

1. Great-Circle 2D distance calculation:

The most common way to calculate the distance between 2 points on the surface of a sphere (as the Earth is simplistically but usually modeled) is the Haversine formula:

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

Although from the great-circle distance wiki page we read:

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.

We can create our own implementation of the Haversine or the Vincenty formula (as shown here for 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 (Haversine):

        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 (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. Adding altitude in the mix:

As mentioned, each of the above calculations yields a great circle distance between 2 points. That distance is also called "as the crow flies", assuming that the "crow" flies without changing altitude and as straight as possible from point A to point B.

We can have a better estimation of the "walking/driving" ("as the crow walks"??) distance by combining the result of one of the previous methods with the difference (delta) in altitude between point A and point B, inside the Euclidean Formula for distance calculation:

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

The previous solution is prone to errors especially the longer the real distance between the points is.
I leave it here for comment continuation reasons.

GeoDjango Distance calculates the 2D distance between two points and doesn't take into consideration the altitude differences.
In order to get the 3D calculation, we need to create a distance function that will consider altitude differences in the calculation:

Theory:

The latitude, longitude and altitude are Polar coordinates and we need to translate them to Cartesian coordinates (x, y, z) in order to apply the Euclidean Formula on them and calculate their 3D distance.

  • Assume:
    polar_point_1 = (long_1, lat_1, alt_1)
    and
    polar_point_2 = (long_2, lat_2, alt_2)

  • Translate each point to it's Cartesian equivalent by utilizing this formula:

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

and you will have p_1 = (x_1, y_1, z_1) and p_2 = (x_2, y_2, z_2) points respectively.

  • Finally use the Euclidean formula:

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...