0

我正在尝试计算/绘制一条线上最靠近另一条线的点。我可以计算那条线上的点,无论是笛卡尔坐标还是大圆距离等。我遇到问题的地方是以英里为单位准确计算距离并绘制它。

我有几行由纬度/经度定义。我试图将其中一条线的终点作为点,p3。P1 和 p2 是相邻线上的两个点。

当我计算交叉轨道距离时,它似乎不正确。(下图)

在此处输入图像描述

附带说明:我可以使用任意数字在笛卡尔坐标中准确计算。在下面使用我的代码:

import numpy as np
import matplotlib.pyplot as plt

x1=1; y1=5
x2=6; y2=7
x0=0; y0=0

p1 = x1,y1
p2 = x2,y2
p3 = x0,y0

d = abs((x2-x1)*(y1-y0) - (x1-x0)*(y2-y1)) / np.sqrt(np.square(x2-x1) + np.square(y2-y1))
print('d = '+str(d))

A = y1-y2
B = x2-x1
C = (x1*y2)-(x2*y1)

d2 = abs( A*x0 + B*y0 + C ) / np.sqrt(np.square(B) + np.square(A))
print('d2 = '+str(d2))

# point along the line at distance d
# https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#:~:text=In%20Euclidean%20geometry%2C%20the%20distance,nearest%20point%20on%20the%20line.
x4 = ( B*(B*x0-A*y0) - A*C ) / (A*A + B*B)
y4 = ( A*(A*y0-B*x0) - B*C ) / (A*A + B*B)

P4 = x4;y4

%matplotlib notebook
fig,ax = plt.subplots(figsize=(5,5))
plt.scatter(x1,y1,color="red",marker=".",label="p1")
plt.scatter(x2,y2,color="blue",marker=".",label="p2")
plt.scatter(x0,y0,color="black",marker=".",label="p3")
plt.scatter(x4,y4,color="green",marker=".",label="p4")

# plt.axline((x1, y1), (x2, y2), linestyle='--', color='black', zorder=0) 
plt.plot((x0, x4), (y0, y4), linestyle=':', color='black', zorder=0)

plt.axis('equal')
plt.legend()

这是情节:

在此处输入图像描述

因此,我可以使方程式起作用,只是难以将它们转换为/从笛卡尔/球形/椭圆形转换并获得准确的距离。

我知道我将这些纬度/经度绘制为笛卡尔图,但它仍然没有在我的脑海中加起来。

这是我用来计算/绘图的代码:

在此网站上找到: https ://gis.stackexchange.com/questions/209540/projecting-cross-track-distance-on-great-circle

from math import radians, cos, sin, asin, sqrt, degrees, atan2

def validate_point(p):
    lat, lon = p
        assert -90 <= lat <= 90, "bad latitude"
        assert -180 <= lon <= 180, "bad longitude"

# original formula from  http://www.movable-type.co.uk/scripts/latlong.html
def distance_haversine(p1, p2):
    """
    Calculate the great circle distance between two points 
    on the earth (specified in decimal degrees)
    Haversine
    formula: 
        a = sin²(Δφ/2) + cos φ1 ⋅ cos φ2 ⋅ sin²(Δλ/2)
                        _   ____
        c = 2 ⋅ atan2( √a, √(1−a) )
        d = R ⋅ c

    where   φ is latitude, λ is longitude, R is earth’s radius (mean radius = 6,371km);
            note that angles need to be in radians to pass to trig functions!
    """
    lat1, lon1 = p1
    lat2, lon2 = p2
    for p in [p1, p2]:
        validate_point(p)

    R = 6371 # km - earths's radius

    # convert decimal degrees to radians 
    lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2])

    # haversine formula 
    dlon = lon2 - lon1
    dlat = lat2 - lat1

    a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
    c = 2 * asin(sqrt(a)) # 2 * atan2(sqrt(a), sqrt(1-a))
    d = R * c # convert to km
    d = d * 0.621371   # convert km to miles
    return d

def spherical2Cart(lat,lon):
    clat=(90-lat)*np.pi/180.
    lon=lon*np.pi/180.
    x=np.cos(lon)*np.sin(clat)
    y=np.sin(lon)*np.sin(clat)
    z=np.cos(clat)

    return np.array([x,y,z])

def cart2Spherical(x,y,z):    
    r=np.sqrt(x**2+y**2+z**2)
    clat=np.arccos(z/r)/np.pi*180
    lat=90.-clat
    lon=np.arctan2(y,x)/np.pi*180
    lon=(lon+360)%360

    return np.array([lat,lon,np.ones(lat.shape)])

def greatCircle(lat1,lon1,lat2,lon2,r=None,verbose=True):
    '''Compute the great circle distance on a sphere

    <lat1>, <lat2>: scalar float or nd-array, latitudes in degree for
                    location 1 and 2.
    <lon1>, <lon2>: scalar float or nd-array, longitudes in degree for
                    location 1 and 2.

    <r>: scalar float, spherical radius.

    Return <arc>: great circle distance on sphere.
    '''
    if r is None:
        r=6371 # km

    d2r=lambda x:x*np.pi/180
    lat1,lon1,lat2,lon2=map(d2r,[lat1,lon1,lat2,lon2])
    dlon=abs(lon1-lon2)

    numerator=(cos(lat2)*sin(dlon))**2 + \
            (cos(lat1)*sin(lat2) - sin(lat1)*cos(lat2)*cos(dlon))**2
    numerator=np.sqrt(numerator)
    denominator=sin(lat1)*sin(lat2)+cos(lat1)*cos(lat2)*cos(dlon)

    dsigma=np.arctan2(numerator,denominator)
    arc=r*dsigma

    return arc


def getCrossTrackPoint(lat1,lon1,lat2,lon2,lat3,lon3):
    '''Get the closest point on great circle path to the 3rd point

    <lat1>, <lon1>: scalar float or nd-array, latitudes and longitudes in
                    degree, start point of the great circle.
    <lat2>, <lon2>: scalar float or nd-array, latitudes and longitudes in
                    degree, end point of the great circle.
    <lat3>, <lon3>: scalar float or nd-array, latitudes and longitudes in
                    degree, a point away from the great circle.

    Return <latp>, <lonp>: latitude and longitude of point P on the great
                           circle that connects P1, P2, and is closest
                           to point P3.
    '''

    x1,y1,z1=spherical2Cart(lat1,lon1)
    x2,y2,z2=spherical2Cart(lat2,lon2)
    x3,y3,z3=spherical2Cart(lat3,lon3)

    D,E,F=np.cross([x1,y1,z1],[x2,y2,z2])

    a=E*z3-F*y3
    b=F*x3-D*z3
    c=D*y3-E*x3

    f=c*E-b*F
    g=a*F-c*D
    h=b*D-a*E

    tt=np.sqrt(f**2+g**2+h**2)
    xp=f/tt
    yp=g/tt
    zp=h/tt

    result1=cart2Spherical(xp,yp,zp)
    result2=cart2Spherical(-xp,-yp,-zp)
    d1=greatCircle(result1[0],result1[1],lat3,lon3,r=1)
    d2=greatCircle(result2[0],result2[1],lat3,lon3,r=1)

    if d1>d2:
        return result2[0],result2[1]
    else:
        return result1[0],result1[1]

def getCrossTrackDistance(lat1,lon1,lat2,lon2,lat3,lon3,r=None):
    '''Compute cross-track distance

    <lat1>, <lon1>: scalar float or nd-array, latitudes and longitudes in
                    degree, start point of the great circle.
    <lat2>, <lon2>: scalar float or nd-array, latitudes and longitudes in
                    degree, end point of the great circle.
    <lat3>, <lon3>: scalar float or nd-array, latitudes and longitudes in
                    degree, a point away from the great circle.

    Return <dxt>: great cicle distance between point P3 to the closest point
                  on great circle that connects P1 and P2.

                  NOTE that the sign of dxt tells which side of the 3rd point
                  P3 is on.

    '''

    if r is None:
        r=CONS.EARTH_RADIUS

    # get angular distance between P1 and P3
    delta13=greatCircle(lat1,lon1,lat3,lon3,r=1.)
    # bearing between P1, P3
    theta13=getBearing(lat1,lon1,lat3,lon3)*np.pi/180
    # bearing between P1, P2
    theta12=getBearing(lat1,lon1,lat2,lon2)*np.pi/180

    dtheta=np.arcsin(sin(delta13)*sin(theta13-theta12))
    dxt=r*dtheta

    return dxt

def getAlongTrackDistance(lat1,lon1,lat2,lon2,lat3,lon3,r=None):
    '''Compute the distance from the start point to closest point to 3rd point

    <lat1>, <lon1>: scalar float or nd-array, latitudes and longitudes in
                    degree, start point of the great circle.
    <lat2>, <lon2>: scalar float or nd-array, latitudes and longitudes in
                    degree, end point of the great circle.
    <lat3>, <lon3>: scalar float or nd-array, latitudes and longitudes in
                    degree, a point away from the great circle.

    Return <dat>: distance from the start point to the closest point on
                  the great circle connecting P1 and P2.

    See also getCrossTrackDistance(), getCrossTrackPoint().
    '''

    if r is None:
        r=CONS.EARTH_RADIUS

    # angular distance from P1 to P3
    delta13=greatCircle(lat1,lon1,lat3,lon3,r=1.)
    # angular distance from Pcloset to P3
    dxt=getCrossTrackDistance(lat1,lon1,lat2,lon2,lat3,lon3,r=1)

    dat=r*np.arccos(cos(delta13)/cos(dxt/r))

    return dat

def getBearing(lat1, long1, lat2, long2):
    import math
    
    dLon = (long2 - long1)

    y = math.sin(dLon) * math.cos(lat2)
    x = math.cos(lat1) * math.sin(lat2) - math.sin(lat1) * math.cos(lat2) * math.cos(dLon)

    brng = math.atan2(y, x)

    brng = np.rad2deg(brng)

    return brng

x1 = -75.9671285; y1 = 41.67304238
x2 = -75.96262168; y2 = 41.66425696
x3 = -75.96017288; y3 = 41.67049662

p1=[x1,y1]
p2=[x2,y2]
p3=[x3,y3]

pp=getCrossTrackPoint(p1[0],p1[1],p2[0],p2[1],p3[0],p3[1],)
print('pp',pp)
dxt=getCrossTrackDistance(p1[0],p1[1],p2[0],p2[1],p3[0],p3[1],r=1)
print('dxt',dxt)
dat=getAlongTrackDistance(p1[0],p1[1],p2[0],p2[1],p3[0],p3[1],r=1)
print('dat',dat)
dxt2=greatCircle(pp[0],pp[1],p3[0],p3[1],r=1)
print('dxt2',dxt2)
dat2=greatCircle(pp[0],pp[1],p1[0],p1[1],r=1)
print('dat2',dat2)

%matplotlib notebook
fig,ax = plt.subplots(figsize=(8,8))
plt.plot(p1[0],p1[1],color="blue",marker="o",label="Great Circle Point p1",markersize=12)
plt.plot(p2[0],p2[1],color="green",marker="o",label="Great Circle Point p2",markersize=12)
plt.plot(p3[0],p3[1],color="red",marker="o",label="Point p3",markersize=12)
plt.plot(pp[0],pp[1],color="Black",marker="*",label="Crosstrack Point",markersize=12)
plt.plot([pp[0],p3[0]],[pp[1],p3[1]],linestyle='--',label='Cross track')
print('Cross track dist = ',distance_haversine(pp,p3))

for q in range(df.shape[0]):
    toe_lat = df['WGS84_toe_lat'].iloc[q]
    toe_lon = df['WGS84_toe_lon'].iloc[q]
    heel_lat = df['WGS84_heel_lat'].iloc[q]
    heel_lon = df['WGS84_heel_lon'].iloc[q]
    plt.plot([toe_lon,heel_lon],[toe_lat,heel_lat],'green')
    if q==0:
        plt.plot([toe_lon,heel_lon],[toe_lat,heel_lat],'green',label="Well Lateral")

plt.ylabel('Latitude')
plt.xlabel('Longitude')
plt.grid(True)
plt.axis('equal')
plt.legend()

当我计算从 p1 到 p3 的半正弦距离时,它计算的是 0.48 英里,但 GIS 软件显示的是 0.4 英里。这几乎不像我需要的那样准确。

在此处输入图像描述

这是否意味着我正在评估的线/点非常接近,以至于笛卡尔坐标会更准确?如果是这样,我如何使用纬度/经度以英里为单位准确计算/绘制它们?

当我尝试将纬度/经度转换为弧度时

具有类似于以下的功能:

def conv_latlon2xy(lat=None,lon=None):
    lat, lon = np.deg2rad(lat), np.deg2rad(lon)
#     R = 6371 # radius of the earth
    x = R * np.cos(lat) * np.cos(lon)
    y = R * np.cos(lat) * np.sin(lon)
    z = R *np.sin(lat)
    return x,y

我没有得到准确的结果。

在这种情况下,使用纬度/经度计算从点到线的最近距离并能够以英里为单位准确测量/绘制距离小于 1 英里的最佳方法是什么?我可以用笛卡尔和球形/椭圆来计算它,但我不能在两者之间进行跳跃。

4

1 回答 1

0

我发现解决此问题的最佳方法是使用 pyproj,其代码类似于以下:

transformer = pyproj.Transformer.from_crs(4326, 2271) # WSG84 -> NAD83PAnorth ft 
df['xx_mid'],df['yy_mid'] =   transformer.transform(df['WGS84_mid_lat'].copy(),df['WGS84_mid_lon'].copy())

这会将纬度/经度转换为英尺或米。

于 2021-05-05T18:37:45.770 回答