本文介绍AQL如何支持地理空间数据的查询,由纬度和经度组成的地理空间坐标可以存储为两个单独的属性,也可以存储为单个属性,该属性是两个值的数组。 ArangoDB 可以索引这些坐标以快速查询地理空间。
准备位置数据
为了简单,我们仍然使用之前已建立的 school 数据库(也可以单独建立新的数据库),在其中建立新的集合,用以存储《权利的游戏》拍摄地点数据.
from pyArango.connection import *
conn = Connection(username="root", password="")
db = conn['school'] # 仍然使用school数据库
# 建立名为 Locations 的普通文档集合
db.createCollection(name="Locations")
insert_query = """
LET places = [
{ "name": "Dragonstone", "coordinate": [ 55.167801, -6.815096 ] },
{ "name": "King's Landing", "coordinate": [ 42.639752, 18.110189 ] },
{ "name": "The Red Keep", "coordinate": [ 35.896447, 14.446442 ] },
{ "name": "Yunkai", "coordinate": [ 31.046642, -7.129532 ] },
{ "name": "Astapor", "coordinate": [ 31.50974, -9.774249 ] },
{ "name": "Winterfell", "coordinate": [ 54.368321, -5.581312 ] },
{ "name": "Vaes Dothrak", "coordinate": [ 54.16776, -6.096125 ] },
{ "name": "Beyond the wall", "coordinate": [ 64.265473, -21.094093 ] }
]
FOR place IN places
INSERT place INTO Locations
"""
db.AQLQuery(insert_query)
随后验证插入的数据,共插入8条位置数据:
all_locations_names = """
FOR p IN Locations
RETURN p.name
"""
query_result = db.AQLQuery(all_locations_names, rawResults=True)
for doc in query_result:
print(doc)
地理空间索引
为了能基于坐标查询,需先建立geo索引, 指定哪个字段存着经纬度数据:
db["Locations"].ensureGeoIndex(["coordinate"])
寻找附近位置
原点(参考点)是爱尔兰都柏林市中心某处的坐标, 寻找离它最近的3处拍摄地:
# 本示例中的 NEAR函数,官方已不再推荐
near_locations_names = """
FOR loc IN NEAR(Locations, 53.35, -6.26, 3, "distance")
RETURN {
name: loc.name,
latitude: loc.coordinate[0],
longitude: loc.coordinate[1],
distance: loc.distance
}
"""
query_result = db.AQLQuery(near_locations_names, rawResults=True)
for doc in query_result:
print(doc)
# 输出:距离以m为单位,由近到远的顺序输出
{'name': 'Vaes Dothrak', 'latitude': 54.16776, 'longitude': -6.096125, 'distance': 91566.58640314484}
{'name': 'Winterfell', 'latitude': 54.368321, 'longitude': -5.581312, 'distance': 121663.99816395003}
{'name': 'Dragonstone', 'latitude': 55.167801, 'longitude': -6.815096, 'distance': 205318.79386198273}
官网最新说明显示,NEAR()函数在3.4版本后不再推荐,官方建议使用DISTANCE(),见下图:
查找指定半径内的位置
使用WITHIN(),可以搜索以参考点为中心,指定半径内的位置,区别在于第4个参数是用户指定的半径,单位是米,示例中以200KM为例(即200,000m)
within_locations_names = """
FOR loc IN WITHIN(Locations, 53.35, -6.26, 200*1000, "distance")
RETURN {
name: loc.name,
latitude: loc.coordinate[0],
longitude: loc.coordinate[1],
distance: loc.distance
}
"""
query_result = db.AQLQuery(within_locations_names, rawResults=True)
for doc in query_result:
print(doc)
# 输出
{'name': 'Vaes Dothrak', 'latitude': 54.16776, 'longitude': -6.096125, 'distance': 91566.58640314484}
{'name': 'Winterfell', 'latitude': 54.368321, 'longitude': -5.581312, 'distance': 121663.99816395003}
同样的,官网在3.4版本之后,也不再推荐使用WITHIN()函数:
计算距离
最后介绍官方目前推荐使用的DISTANCE()函数,它使用基于球形地球模型的半正弦公式(haversine formula)计算两个任意坐标之间以米为单位的距离,该函数计算速度很快,能精确到 0.3% 左右,这对于大多数场景(例如位置感知服务)来说已经足够了。
若使用DISTANCE()实现刚才WITHIN()的功能,则AQL可以这么写:
within_locations_names = """
FOR doc IN Locations
LET d = DISTANCE(doc.coordinate[0], doc.coordinate[1], 53.35, -6.26)
FILTER d / 1000 <= 200
SORT d ASC
RETURN {
name: doc.name,
latitude: doc.coordinate[0],
longitude: doc.coordinate[1],
distance: d / 1000
}
"""
query_result = db.AQLQuery(within_locations_names, rawResults=True)
for doc in query_result:
print(doc)
# 输出,本次以KM为单位
{'name': 'Vaes Dothrak', 'latitude': 54.16776, 'longitude': -6.096125, 'distance': 91.56658640314484}
{'name': 'Winterfell', 'latitude': 54.368321, 'longitude': -5.581312, 'distance': 121.66399816395003}
同理,使用DISTANCE()实现刚才NEAR()的功能,代码如下:
near_locations = """
FOR doc IN Locations
LET d = DISTANCE(doc.coordinate[0], doc.coordinate[1], 53.35, -6.26)
SORT d ASC
LIMIT 3
RETURN {
name: doc.name,
latitude: doc.coordinate[0],
longitude: doc.coordinate[1],
distance: d / 1000
}
"""
query_result = db.AQLQuery(near_locations, rawResults=True)
for doc in query_result:
print(doc)