【问题标题】:SciKits BallTree method gives me incorrect "nearest neighbor"SciKits BallTree 方法给了我不正确的“最近邻居”
【发布时间】:2021-07-19 20:51:03
【问题描述】:

我正在使用下面给出的源代码来获取最近的“站点”。

来源:https://automating-gis-processes.github.io/site/notebooks/L3/nearest-neighbor-faster.html

我的代码:

# Read data from a DB
test_df = pd.read_sql_query(sql, conn)

# Calculates distance between 2 points on a map using lat and long 
# (Source: https://towardsdatascience.com/heres-how-to-calculate-distance-between-2-geolocations-in-python-93ecab5bbba4)
def haversine_distance(lat1, lon1, lat2, lon2):
   r = 6371
   phi1 = np.radians(float(lat1))
   phi2 = np.radians(float(lat2))
   delta_phi = np.radians(lat2 - lat1)
   delta_lambda = np.radians(lon2- lon1)
   a = np.sin(delta_phi / 2)**2 + np.cos(phi1) * np.cos(phi2) *   np.sin(delta_lambda / 2)**2
   res = r * (2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a)))
   return np.round(res, 2)

test_df["actualDistance (km)"] = test_df.apply(lambda row: haversine_distance(row['ClientLat'],row['ClientLong'],row['actual_SLa'],row['actual_SLo']), axis=1)

test_gdf = geopandas.GeoDataFrame(test_df, geometry=geopandas.points_from_xy(test_df.ClientLong, test_df.ClientLat))
site_gdf = geopandas.GeoDataFrame(site_df, geometry=geopandas.points_from_xy(site_df.SiteLong, site_df.SiteLat))

#-------Set up the functions as shown in the tutorial-------

def get_nearest(src_points, candidates, k_neighbors=1):
    """Find nearest neighbors for all source points from a set of candidate points"""

    # Create tree from the candidate points
    tree = BallTree(candidates, leaf_size=15, metric='haversine')

    # Find closest points and distances
    distances, indices = tree.query(src_points, k=k_neighbors)

    # Transpose to get distances and indices into arrays
    distances = distances.transpose()
    indices = indices.transpose()

    # Get closest indices and distances (i.e. array at index 0)
    # note: for the second closest points, you would take index 1, etc.
    closest = indices[0]
    closest_dist = distances[0]

    # Return indices and distances
    return (closest, closest_dist)


def nearest_neighbor(left_gdf, right_gdf, return_dist=False):
    """
    For each point in left_gdf, find closest point in right GeoDataFrame and return them.

    NOTICE: Assumes that the input Points are in WGS84 projection (lat/lon).
    """

    left_geom_col = left_gdf.geometry.name
    right_geom_col = right_gdf.geometry.name

    # Ensure that index in right gdf is formed of sequential numbers
    right = right_gdf.copy().reset_index(drop=True)

    # Parse coordinates from points and insert them into a numpy array as RADIANS
    left_radians = np.array(left_gdf[left_geom_col].apply(lambda geom: (geom.x * np.pi / 180, geom.y * np.pi / 180)).to_list())
    right_radians = np.array(right[right_geom_col].apply(lambda geom: (geom.x * np.pi / 180, geom.y * np.pi / 180)).to_list())

    # Find the nearest points
    # -----------------------
    # closest ==> index in right_gdf that corresponds to the closest point
    # dist ==> distance between the nearest neighbors (in meters)

    closest, dist = get_nearest(src_points=left_radians, candidates=right_radians)

    # Return points from right GeoDataFrame that are closest to points in left GeoDataFrame
    closest_points = right.loc[closest]

    # Ensure that the index corresponds the one in left_gdf
    closest_points = closest_points.reset_index(drop=True)

    # Add distance if requested
    if return_dist:
        # Convert to meters from radians
        earth_radius = 6371000  # meters
        closest_points['distance'] = dist * earth_radius

    return closest_points

closest_sites = nearest_neighbor(test_gdf, site_gdf, return_dist=True)

# Rename the geometry of closest sites gdf so that we can easily identify it
closest_sites = closest_sites.rename(columns={'geometry': 'closest_site_geom'})

# Merge the datasets by index (for this, it is good to use '.join()' -function)
test_gdf = test_gdf.join(closest_sites)

#Extracted closest site latitude and longitude for data analysis
test_gdf['CS_lo'] = test_gdf.closest_site_geom.apply(lambda p: p.x)
test_gdf['CS_la'] = test_gdf.closest_site_geom.apply(lambda p: p.y)

代码是我提供的教程链接的副本。根据他们的解释,它应该有效。

为了验证这些数据,我使用.describe() 获得了一些统计数据,它表明教程方法确实给了我一个比实际数据中的距离更近的平均距离(792 m vs 实际距离1.80 公里)。 Closest Distance generated using the BallTree method Actual Distance in the data

但是,当我使用 plotly 在地图上绘制它们时,我注意到 BallTree 方法的输出并不比“实际”距离更近。 This is generally what the plotted data looks like (Blue: predetermined site, Red: site predicted using the BallTree method 有人可以帮我找出差异

【问题讨论】:

    标签: python scikit-learn data-analysis nearest-neighbor


    【解决方案1】:

    我不确定为什么会这样,但确实如此。我决定只根据文档编写代码而不是按照教程编写代码,这很有效:

    # Build BallTree with haversine distance metric, which expects (lat, lon) in radians and returns distances in radians
    dist = DistanceMetric.get_metric('haversine')
    tree = BallTree(np.radians(site_df[['SiteLat', 'SiteLong']]), metric=dist)
    
    test_coords = np.radians(test_df[['ClientLat', 'ClientLong']])
    dists, ilocs = tree.query(test_coords)
    

    【讨论】:

      【解决方案2】:

      问题在于教程代码以Longitude, Latitude 格式提供坐标,而不是 BallTree 预期的Latitude, Longitude 格式。所以你正在测量倒置点之间的距离。

      如果您在坐标解析代码中交换 geom.x 和 geom.y 的顺序,您将获得正确的测量结果。

      # Parse coordinates from points and insert them into a numpy array as RADIANS
          left_radians = np.array(left_gdf[left_geom_col].apply(lambda geom: (geom.y * np.pi / 180, geom.x * np.pi / 180)).to_list())
          right_radians = np.array(right[right_geom_col].apply(lambda geom: (geom.y * np.pi / 180, geom.x * np.pi / 180)).to_list())
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-09-19
        • 2018-09-06
        • 2020-11-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多