在 Python 中测量 1000 中两个经纬度点之间的距离
Measuring distance between two lat long points in 1000's in Python
我有两个数据框。
df1 有 580 条唯一记录 - 带有纬度和经度信息
df2 有 490000 条唯一记录 - 带有经纬度信息
我正在尝试获取 - 在这 580 个位置中,490000 个位置的 400 米半径范围内有多少个位置。
我正在使用以下代码并且它正在运行。
from __future__ import print_function
from config import conn
from pandas import DataFrame
import pandas as pd
import math
def distance(origin, destination):
lat1, lon1 = origin
lat2, lon2 = destination
radius = 6371 *1000# km
dlat = math.radians(lat2-lat1)
dlon = math.radians(lon2-lon1)
a = math.sin(dlat/2) * math.sin(dlat/2) + math.cos(math.radians(lat1)) \
* math.cos(math.radians(lat2)) * math.sin(dlon/2) * math.sin(dlon/2)
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
d = radius * c
return d
def convertTuple(tup):
str = ''.join(tup)
return str
df1 = pd.read_csv("/home/ubuntu/maid80.csv")
df2 = pd.read_csv("/home/ubuntu/iodr.csv")
ll = []
for index,rows in df2.iterrows():
lat1 = rows['latitude']
lon1 = rows['longitude']
for i,r in df1.iterrows():
k = distance((lat1,lon1),(r['latitude'],r['longitude']))
if (k <= 400):
ll.append(rows['id'])
# print(ll)
print(index)
myset = set(ll)
print(myset)
我 运行 这不是我的笔记本电脑,完成所有 580 次迭代需要 2 个多小时。我担心第二个数据集中的记录数量会膨胀。
有没有更好的方法,可以节省时间。
按纬度对两个数据框进行排序。如果它们的纬度差异很大,这将允许不计算点对之间的距离。在最好的情况下,您可以获得 580 倍的加速。
想法是遍历 df1 的行,并为该数组的每一行找到第二个数组的左右索引,这些索引的纬度离该行不远
df1.sort_values(by='latitude')
df2.sort_values(by='latitude')
n1 = df1.shape[0]
n2 = df2.shape[0]
left = 0
right = 0
threshold = 400
lat_threshold = threshold / radius # latitude difference that corresponds to 400 m
for i in range(n1):
row1 = df1.iloc[[i]]
lat1 = row1['latitude']
lon1 = row1['longitude']
while left < n2 and df2.iloc[[left]]['latitude'] < lat1 - lat_threshold:
left += 1
while right < n2 and df2.iloc[[right]]['latitude'] < lat1 + lat_threshold:
right += 1
for j in range(left, right):
row2 = df2.iloc[[j]]
lat2 = row2['latitude']
lon2 = row2['longitude']
k = distance((lat1, lon1), (lat2, lon2))
if (k <= threshold):
ll.append(row2)
您可以使用 geopandas 进行尝试:
import geopandas as gpd
import pandas as pd
import pyproj
df1 = pd.read_csv("/home/ubuntu/maid80.csv")
df2 = pd.read_csv("/home/ubuntu/iodr.csv")
gdf1 = gpd.GeoDataFrame(df1, geometry=gpd.points_from_xy(df1['longitude'], df1['latitude']), crs=pyproj.CRS.from_epsg(4326))
gdf2 = gpd.GeoDataFrame(df2, geometry=gpd.points_from_xy(df2['longitude'], df2['latitude']), crs=pyproj.CRS.from_epsg(4326))
radius = 400
for gdf in [gdf1, gdf2]:
gdf.to_crs(pyproj.CRS.from_epsg(3857), inplace=True)
gdf1['geometry'] = gdf1['geometry'].buffer(radius)
gdf2['IS_WITHIN_400M'] = 1
gdf = gpd.sjoin(gdf1, gdf2['geometry'], how='left')
print(gdf[gdf.IS_WITHIN_400M_right==1].head())
一些解释:
Geopandas 将允许您使用 GeoDataFrame,您可以在其上使用半径(非常快)“缓冲”您的点。 points_from_xy 函数也非常快,可以让您高效地构建这些对象。
sjoin 方法(代表空间连接)也很快。我怀疑这与包含边界框 and/or 排序坐标的算法有关...我使用此方法取得了一些不错的结果。
警告:
我将数据集投影到 EPSG 3857 中,它是全局的 AND 具有笛卡尔坐标(以米为单位)。对于任何“真实”项目,您必须仔细选择投影(即选择您所在地区最好的“公制友好”投影)以避免缓冲区失真...
您只能对距离函数使用 numpy 函数并将其矢量化。那应该快很多:
from __future__ import print_function
import pandas as pd
import math
import numpy as np
def distance(origin: pd.DataFrame, lat2, lon2):
'''Measure distance not for a pair but for the whole dataframa against one point'''
lat1 = origin['latitude']
lon1 = origin['longitude']
radius = 6371 * 1000 # km
dlat = np.radians(lat2 - lat1)
dlon = np.radians(lon2 - lon1)
a = np.sin(dlat / 2) * np.sin(dlat / 2) + np.cos(np.radians(lat1)) \
* np.cos(np.radians(lat2)) * np.sin(dlon / 2) * np.sin(dlon / 2)
c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a))
d = radius * c
return d
def main():
df1 = pd.read_csv("/home/ubuntu/maid80.csv")
df2 = pd.read_csv("/home/ubuntu/iodr.csv")
ll = []
for index, row in df2.iterrows():
#because you can test the whole dataframe gainst one point you can remove one loop.
mask= distance(df1,row['latitude'],row['longitude'])<400.0
ll.extend(df1.index[mask].to_list()) #only add points to the list where the distance is <400
myset = set(ll)
print(myset)
也许您必须切换数据帧。不知道你要从哪一个收集id。
您可以使用 BallTree with HaversineDistance 指标。首先使用第一个 table 的坐标构建树,然后从该树
的第二个 table 查询坐标
from sklearn.neighbors import BallTree, DistanceMetric
radius = 6371 * 1000
max_distance = 400 / radius
# ensure that format of array is [latitude, longitude]
rows1 = np.deg2rad(df1[['latitude', 'longitude']].to_numpy())
rows2 = np.deg2rad(df2[['latitude', 'longitude']].to_numpy())
# haversine metric accepts latitude and longitude only in radians and returns distance
# on unit sphere
tree = BallTree(rows1, metric=DistanceMetric.get_metric('haversine'))
count = tree.query_radius(rows2, r=max_distance, count_only=True)
print(df2['id'].iloc[np.nonzero(count)[0]])
我有两个数据框。
df1 有 580 条唯一记录 - 带有纬度和经度信息
df2 有 490000 条唯一记录 - 带有经纬度信息
我正在尝试获取 - 在这 580 个位置中,490000 个位置的 400 米半径范围内有多少个位置。
我正在使用以下代码并且它正在运行。
from __future__ import print_function
from config import conn
from pandas import DataFrame
import pandas as pd
import math
def distance(origin, destination):
lat1, lon1 = origin
lat2, lon2 = destination
radius = 6371 *1000# km
dlat = math.radians(lat2-lat1)
dlon = math.radians(lon2-lon1)
a = math.sin(dlat/2) * math.sin(dlat/2) + math.cos(math.radians(lat1)) \
* math.cos(math.radians(lat2)) * math.sin(dlon/2) * math.sin(dlon/2)
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
d = radius * c
return d
def convertTuple(tup):
str = ''.join(tup)
return str
df1 = pd.read_csv("/home/ubuntu/maid80.csv")
df2 = pd.read_csv("/home/ubuntu/iodr.csv")
ll = []
for index,rows in df2.iterrows():
lat1 = rows['latitude']
lon1 = rows['longitude']
for i,r in df1.iterrows():
k = distance((lat1,lon1),(r['latitude'],r['longitude']))
if (k <= 400):
ll.append(rows['id'])
# print(ll)
print(index)
myset = set(ll)
print(myset)
我 运行 这不是我的笔记本电脑,完成所有 580 次迭代需要 2 个多小时。我担心第二个数据集中的记录数量会膨胀。
有没有更好的方法,可以节省时间。
按纬度对两个数据框进行排序。如果它们的纬度差异很大,这将允许不计算点对之间的距离。在最好的情况下,您可以获得 580 倍的加速。
想法是遍历 df1 的行,并为该数组的每一行找到第二个数组的左右索引,这些索引的纬度离该行不远
df1.sort_values(by='latitude')
df2.sort_values(by='latitude')
n1 = df1.shape[0]
n2 = df2.shape[0]
left = 0
right = 0
threshold = 400
lat_threshold = threshold / radius # latitude difference that corresponds to 400 m
for i in range(n1):
row1 = df1.iloc[[i]]
lat1 = row1['latitude']
lon1 = row1['longitude']
while left < n2 and df2.iloc[[left]]['latitude'] < lat1 - lat_threshold:
left += 1
while right < n2 and df2.iloc[[right]]['latitude'] < lat1 + lat_threshold:
right += 1
for j in range(left, right):
row2 = df2.iloc[[j]]
lat2 = row2['latitude']
lon2 = row2['longitude']
k = distance((lat1, lon1), (lat2, lon2))
if (k <= threshold):
ll.append(row2)
您可以使用 geopandas 进行尝试:
import geopandas as gpd
import pandas as pd
import pyproj
df1 = pd.read_csv("/home/ubuntu/maid80.csv")
df2 = pd.read_csv("/home/ubuntu/iodr.csv")
gdf1 = gpd.GeoDataFrame(df1, geometry=gpd.points_from_xy(df1['longitude'], df1['latitude']), crs=pyproj.CRS.from_epsg(4326))
gdf2 = gpd.GeoDataFrame(df2, geometry=gpd.points_from_xy(df2['longitude'], df2['latitude']), crs=pyproj.CRS.from_epsg(4326))
radius = 400
for gdf in [gdf1, gdf2]:
gdf.to_crs(pyproj.CRS.from_epsg(3857), inplace=True)
gdf1['geometry'] = gdf1['geometry'].buffer(radius)
gdf2['IS_WITHIN_400M'] = 1
gdf = gpd.sjoin(gdf1, gdf2['geometry'], how='left')
print(gdf[gdf.IS_WITHIN_400M_right==1].head())
一些解释:
Geopandas 将允许您使用 GeoDataFrame,您可以在其上使用半径(非常快)“缓冲”您的点。 points_from_xy 函数也非常快,可以让您高效地构建这些对象。
sjoin 方法(代表空间连接)也很快。我怀疑这与包含边界框 and/or 排序坐标的算法有关...我使用此方法取得了一些不错的结果。
警告:
我将数据集投影到 EPSG 3857 中,它是全局的 AND 具有笛卡尔坐标(以米为单位)。对于任何“真实”项目,您必须仔细选择投影(即选择您所在地区最好的“公制友好”投影)以避免缓冲区失真...
您只能对距离函数使用 numpy 函数并将其矢量化。那应该快很多:
from __future__ import print_function
import pandas as pd
import math
import numpy as np
def distance(origin: pd.DataFrame, lat2, lon2):
'''Measure distance not for a pair but for the whole dataframa against one point'''
lat1 = origin['latitude']
lon1 = origin['longitude']
radius = 6371 * 1000 # km
dlat = np.radians(lat2 - lat1)
dlon = np.radians(lon2 - lon1)
a = np.sin(dlat / 2) * np.sin(dlat / 2) + np.cos(np.radians(lat1)) \
* np.cos(np.radians(lat2)) * np.sin(dlon / 2) * np.sin(dlon / 2)
c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a))
d = radius * c
return d
def main():
df1 = pd.read_csv("/home/ubuntu/maid80.csv")
df2 = pd.read_csv("/home/ubuntu/iodr.csv")
ll = []
for index, row in df2.iterrows():
#because you can test the whole dataframe gainst one point you can remove one loop.
mask= distance(df1,row['latitude'],row['longitude'])<400.0
ll.extend(df1.index[mask].to_list()) #only add points to the list where the distance is <400
myset = set(ll)
print(myset)
也许您必须切换数据帧。不知道你要从哪一个收集id。
您可以使用 BallTree with HaversineDistance 指标。首先使用第一个 table 的坐标构建树,然后从该树
的第二个 table 查询坐标from sklearn.neighbors import BallTree, DistanceMetric
radius = 6371 * 1000
max_distance = 400 / radius
# ensure that format of array is [latitude, longitude]
rows1 = np.deg2rad(df1[['latitude', 'longitude']].to_numpy())
rows2 = np.deg2rad(df2[['latitude', 'longitude']].to_numpy())
# haversine metric accepts latitude and longitude only in radians and returns distance
# on unit sphere
tree = BallTree(rows1, metric=DistanceMetric.get_metric('haversine'))
count = tree.query_radius(rows2, r=max_distance, count_only=True)
print(df2['id'].iloc[np.nonzero(count)[0]])