空间过程
合格名称 | 类型 |
---|---|
apoc.spatial.geocode |
|
apoc.spatial.reverseGeocode |
|
apoc.spatial.sortByDistance |
|
地理编码
geocode 过程将文本地址转换为包含纬度、经度和描述的位置。尽管它只是一个函数,但结合内置函数 point 和 distance,我们可以获得非常强大的结果。
首先,我们如何使用此过程
CALL apoc.spatial.geocodeOnce('21 rue Paul Bellamy 44000 NANTES FRANCE')
YIELD location
RETURN location.latitude, location.longitude
location.latitude | location.longitude |
---|---|
47.2221667 |
-1.5566625 |
该过程有三种形式
-
geocodeOnce(address) 返回零个或一个结果。
-
geocode(address,maxResults) 返回零个、一个或多个(最多 maxResults)结果。
-
reverseGeocode(latitude,longitude) 返回零个或一个结果。
这是因为后端地理编码服务(OSM、Google、OpenCage 或其他)可能对同一查询返回多个结果。GeocodeOnce() 的设计目的是返回第一个或排名最高的结果。
第三个过程 reverseGeocode 会将包含纬度和经度的位置转换为文本地址。
CALL apoc.spatial.reverseGeocode(47.2221667,-1.5566625) YIELD location
RETURN location.description;
location.description |
---|
"21, Rue Paul Bellamy, Talensac - Pont Morand, Hauts-Pavés - Saint-Félix, Nantes, Loire-Atlantique, Pays de la Loire, France métropolitaine, 44000, France" |
配置地理编码
在 apoc.conf 文件中或通过 $config
参数(参见下面的 通过配置参数映射进行配置
部分)可以设置一些选项来控制服务。
在 apoc.conf
中我们可以传递
-
apoc.spatial.geocode.provider=osm (osm, google, opencage 等)
-
apoc.spatial.geocode.osm.throttle=5000 (查询 OSM 服务器之间的延迟毫秒数,以避免过载)
-
apoc.spatial.geocode.google.throttle=1 (查询 Google 服务器之间的延迟毫秒数,以避免过载)
-
apoc.spatial.geocode.google.key=xxxx (Google 地理编码访问的 API 密钥)
-
apoc.spatial.geocode.google.client=xxxx (Google 地理编码访问的客户端代码)
-
apoc.spatial.geocode.google.signature=xxxx (Google 地理编码访问的客户端签名)
对于 Google,您应该使用密钥或客户端和签名的组合。有关详细信息,请访问 Google 地理编码访问页面:https://developers.google.com/maps/documentation/geocoding/get-api-key#key
配置自定义地理编码提供商
地理编码
对于除 'osm' 或 'google' 之外的任何提供商,您将获得一个可配置的供应商,需要两个额外的设置:'url' 和 'key'。'url' 必须包含 'PLACE' 和 'KEY' 这两个单词。'KEY' 将被替换为您注册服务时从提供商处获得的密钥。当调用过程时,'PLACE' 将被替换为要进行地理编码的地址。
逆地理编码
'url' 必须包含 'LAT'、'LNG' 和 'KEY' 这三个单词。当调用过程时,'LAT' 将被替换为纬度,'LNG' 将被替换为要进行逆地理编码的经度。
例如,要使服务与 OpenCage 配合使用,请执行以下步骤
-
在 https://geocoder.opencagedata.com/ 注册您自己的应用程序密钥
-
获得密钥后,将以下三行添加到 apoc.conf
apoc.spatial.geocode.provider=opencage apoc.spatial.geocode.opencage.key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX apoc.spatial.geocode.opencage.url=http://api.opencagedata.com/geocode/v1/json?q=PLACE&key=KEY apoc.spatial.geocode.opencage.reverse.url=http://api.opencagedata.com/geocode/v1/json?q=LAT+LNG&key=KEY
-
请确保上述 'XXXXXXX' 部分已替换为您的实际密钥
-
重启 Neo4j 服务器,然后测试地理编码过程以验证其是否正常工作
通过配置参数映射进行配置
另外,我们可以传递一个配置映射。
请注意,这些配置优先于 apoc.conf
设置。
我们可以传递一个提供商密钥,它将等同于 apoc.spatial.geocode.provider
设置密钥,其他密钥将等同于 apoc.spatial.geocode.<PROVIDER>.<KEY>
设置。
例如
CALL apoc.spatial.geocodeOnce('<MY_PLACE>', {
provider: 'opencage',
url: 'http://api.opencagedata.com/geocode/v1/json?q=PLACE&key=KEY',
reverseUrl: 'http://api.opencagedata.com/geocode/v1/json?q=LAT+LNG&key=KEY',
key: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
})
等同于这些(请注意,我们将 UpperCamelCase
键转换为 dot.case
,例如从 reverseUrl
转换为 reverse.url
)
apoc.spatial.geocode.provider=opencage apoc.spatial.geocode.opencage.key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX apoc.spatial.geocode.opencage.url=http://api.opencagedata.com/geocode/v1/json?q=PLACE&key=KEY apoc.spatial.geocode.opencage.reverse.url=http://api.opencagedata.com/geocode/v1/json?q=LAT+LNG&key=KEY
如果我们没有通过配置映射传递提供商,将选择 apoc.spatial.geocode.provider
设置,否则选择默认的 'osm'
。例如
/* apoc.conf
...
apoc.spatial.geocode.provider=google
...
*/
CALL apoc.spatial.geocodeOnce('<MY_PLACE>', {key: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'})
将传递一个类似 apoc.spatial.geocode.google.key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
的配置。
在更大的 Cypher 查询中使用地理编码
一个更复杂或更有用的例子,它对节点属性中找到的地址进行地理编码
MATCH (a:Place)
WHERE a.address IS NOT NULL
CALL apoc.spatial.geocodeOnce(a.address) YIELD location
RETURN location.latitude AS latitude, location.longitude AS longitude, location.description AS description
计算地点之间的距离
如果我们希望计算地址之间的距离,我们需要使用 point() 函数将纬度和经度转换为 Cypher Point 类型,然后使用 point.distance() 函数计算距离
WITH point({latitude: 48.8582532, longitude: 2.294287}) AS eiffel
MATCH (a:Place)
WHERE a.address IS NOT NULL
CALL apoc.spatial.geocodeOnce(a.address) YIELD location
WITH location, point.distance(point(location), eiffel) AS distance
WHERE distance < 5000
RETURN location.description AS description, distance
ORDER BY distance
LIMIT 100
sortByDistance
第二个过程允许您根据节点上的经纬度属性,按路径的距离总和对给定 PATH
值集合进行排序。
示例数据
CREATE (bruges:City {name:"bruges", latitude: 51.2605829, longitude: 3.0817189})
CREATE (brussels:City {name:"brussels", latitude: 50.854954, longitude: 4.3051786})
CREATE (paris:City {name:"paris", latitude: 48.8588376, longitude: 2.2773455})
CREATE (dresden:City {name:"dresden", latitude: 51.0767496, longitude: 13.6321595})
MERGE (bruges)-[:NEXT]->(brussels)
MERGE (brussels)-[:NEXT]->(dresden)
MERGE (brussels)-[:NEXT]->(paris)
MERGE (bruges)-[:NEXT]->(paris)
MERGE (paris)-[:NEXT]->(dresden)
查找路径并按距离排序
MATCH (a:City {name:'bruges'}), (b:City {name:'dresden'})
MATCH p=(a)-[*]->(b)
WITH collect(p) as paths
CALL apoc.spatial.sortByDistance(paths) YIELD path, distance
RETURN path, distance
图重构
为了避免在多个查询中重复对同一事物进行地理编码,特别是如果数据库将被许多人使用,最好将结果持久化到数据库中,以便后续调用可以使用保存的结果。
地理编码并持久化结果
MATCH (a:Place)
WHERE a.address IS NOT NULL AND a.latitude IS NULL
WITH a LIMIT 1000
CALL apoc.spatial.geocodeOnce(a.address) YIELD location
SET a.latitude = location.latitude
SET a.longitude = location.longitude
请注意,上述命令仅对前 1000 个尚未进行地理编码的“地点”节点进行地理编码。此查询可以多次运行,直到所有地点都完成地理编码。我们为什么要这样做?有两个很好的理由
-
地理编码服务是一个公共服务,可能会限制或将频繁访问的服务列入黑名单,因此控制我们的访问量很有用。
-
事务正在更新数据库,明智的做法是在同一事务中不要更新太多东西,以避免占用太多内存。这个技巧将使内存使用量非常低。
现在在距离查询中使用结果
WITH point({latitude: 48.8582532, longitude: 2.294287}) AS eiffel
MATCH (a:Place)
WHERE a.latitude IS NOT NULL AND a.longitude IS NOT NULL
WITH a, point.distance(point(a), eiffel) AS distance
WHERE distance < 5000
RETURN a.name, distance
ORDER BY distance
LIMIT 100
空间与时间组合搜索
结合空间和日期时间函数可以实现更复杂的查询
WITH point({latitude: 48.8582532, longitude: 2.294287}) AS eiffel
MATCH (e:Event)
WHERE e.address IS NOT NULL AND e.datetime IS NOT NULL
CALL apoc.spatial.geocodeOnce(e.address) YIELD location
WITH e, location,
distance(point(location), eiffel) AS distance,
(apoc.date.parse('2016-06-01 00:00:00','h') - apoc.date.parse(e.datetime,'h'))/24.0 AS days_before_due
WHERE distance < 5000 AND days_before_due < 14 AND apoc.date.parse(e.datetime,'h') < apoc.date.parse('2016-06-01 00:00:00','h')
RETURN e.name AS event, e.datetime AS date,
location.description AS description, distance
ORDER BY distance