空间过程

空间过程使您的数据具备地理空间功能,并补充了 Neo4j 自带的空间函数。更全面的空间功能可以在Neo4j 空间库中找到。

合格名称 类型

apoc.spatial.geocode
apoc.spatial.geocode(location STRING, maxResults INTEGER, quotaException BOOLEAN, config MAP<STRING, ANY>)) - 使用地理编码服务(默认:OpenStreetMap)返回给定地址的地理位置(纬度、经度和描述)。

过程

apoc.spatial.reverseGeocode
apoc.spatial.reverseGeocode(latitude FLOAT, longitude FLOAT, quotaException BOOLEAN, config MAP<STRING, ANY>) - 使用地理编码服务(默认:OpenStreetMap)从给定地理位置(纬度、经度)返回文本地址。此过程最多返回一个结果。

过程

apoc.spatial.sortByDistance
apoc.spatial.sortByDistance(paths LIST<PATH>) - 根据 NODE 值中的纬度/经度值,按其距离总和对给定 PATH 值集合进行排序。

过程

地理编码

geocode 过程将文本地址转换为包含纬度经度描述的位置。尽管它只是一个函数,但结合内置函数 pointdistance,我们可以获得非常强大的结果。

首先,我们如何使用此过程

CALL apoc.spatial.geocodeOnce('21 rue Paul Bellamy 44000 NANTES FRANCE')
YIELD location
RETURN location.latitude, location.longitude
表 1. 结果
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;
表 2. 结果
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 配合使用,请执行以下步骤

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
© . All rights reserved.