亚马逊AWS官方博客

手把手带您玩转云原生文档数据库DocumentDB的地理空间

当前企业应用场景中都会广泛使用到文档数据库例如内容和目录管理、配置文件管理、移动和Web应用程序等。

Amazon DocumentDB(兼容 MongoDB)是一项快速、可扩展、高度可用且完全托管的云原生文档数据库服务,支持 MongoDB 工作负载。作为一个文档数据库,Amazon DocumentDB 使得存储、查询和索引 JSON 数据变得简单和高效。

同时Amazon DocumentDB云原生文档数据库服务,可以向用户提供运行关键任务型文档数据库工作负载时所需的高性能、高扩展性和高可用性。

本博文将通过示例的餐馆地理信息导入、存储、2dsphere索引创建和查询,向您深入介绍 Amazon DocumentDB的地理空间查询功能,特别是$nearSphere查询运算符和$geoNear聚合管道运算等, 手把手带您玩转云原生文档数据库DocumentDB的地理空间。

借助 Amazon DocumentDB 中对地理空间查询功能的额外支持,您可以在文档数据库中轻松回答基于地理信息的查询诸如 移动和Web应用程序查询“哪三家餐厅离我当前位置最近?”或车企提供给车主的自助服务应用查询“离我最近的电动车充电桩在哪里?等

DocumentDB如何存储地理信息

Amazon DocumentDB 使用GeoJSON表示地理空间数据。GeoJSON 是一种开源规范,用于在坐标空间中对形状进行 JSON 格式。GeoJSON 坐标同时捕获经度和纬度,表示类似地球的球体上的位置。

Amazon DocumentDB使用“点”GeoJSON 类型来存储地理空间数据。每个 GeoJSON 文档(或子文档)通常由两个字段组成:

  • 类型-所表示的形状,它告知Amazon DocumentDB如何解释 “坐标” 字段。目前,Amazon DocumentDB支持“点”(point)类型
  • 坐标— 表示为数组中对象的纬度和经度对 — [经度、纬度]
  • 例如,您可以使用以下结构在DocumentDB中创建一个名为 restaurants 的餐馆集合,并记下每个餐馆的基本信息、评分情况以及位置信息等:
db.restaurants.insert({
   "state":"Washington",
   "city":"Seattle",
   "name":"Thai Palace",
   "rating": 4.8,
   "location":{
      "type":"Point",
      "coordinates":[
         -122.3264,
         47.6009
      ]
   }
});

Amazon DocumentDB还使用 2dsphere 索引为地理空间数据编制索引。Amazon DocumentDB  支持索引点,Amazon DocumentDB  支持使用 2dsphere 索引进行邻近查询。

在DocumentDB中查询地理空间数据

Amazon DocumentDB 支持地理空间数据的邻近、包含和交叉点查询。邻近度查询的一个很好的例子是查找距离一个点(城市)小于一定距离的所有点(所有机场)。包含查询的一个很好的例子是查找位于指定区域/多边形(浙江省)中的所有点(所有机场)。交叉点查询的一个很好的例子是找到一个与点(城市 例如杭州市)相交的多边形(省份)。您可以使用以下地理空间运算符从数据中实现基于地理信息分析。

  • $nearSphere-$nearSphere是一个查找运算符,支持从最接近到最远的 GeoJSON 点查找点。
  • $geoNear-$geoNear是一个聚合运算符,支持计算距离 GeoJSON 点的距离(以米为单位)。
  • $minDistance-$minDistance是与配合使用的查找运算符$nearSphere或者 $geoNear筛选距离中心点至少达到指定最小距离的文档。
  • $maxDistance-$maxDistance是与配合使用的查找运算符$nearSphere或者$geoNear筛选距离中心点最多达到指定最大距离的文档。
  • $geoWithin-$geoWithin是一个查找运算符,支持使用完全存在于指定形状(如多边形)中的地理空间数据查找文档。
  • $geoIntersects-$geoIntersects是一个查找运算符,支持查找地理空间数据与指定 GeoJSON 对象相交的文档。

DocumenentDB中准备示例餐馆地理信息数据

在EC2堡垒机下载地理数据:

wget https://raw.githubusercontent.com/mongodb/docs-assets/geospatial/neighborhoods.json .
 
wget https://raw.githubusercontent.com/mongodb/docs-assets/geospatial/restaurants.json .

在EC2堡垒机上安装数据导入工具mongoimport:

sudo yum install mongodb-org-tools

通过mongoimport导入地理数据到DocumentDB (本示例SSL关闭状态 可基于您的SSL状态 增加SSL选项):

mongoimport  --host=【your DocumentDB cluster endpoint】--collection=neighborhoods --db=geo --file=neighborhoods.json  --username=【your username】 --password=【your password】

(import 195 documents)

mongoimport  --host=【your DocumentDB cluster endpoint】 --collection=restaurants --db=geo --file=restaurants.json --username=【your username】 --password=【your password】

(imported 25359 documents)

创建Collection restaurants 2dsphere索引:

db.restaurants.createIndex({ location: "2dsphere" })

DocumentDB中执行示例餐馆地理信息数据查询

查询1:饭店基本信息查询:

rs0:PRIMARY> db.restaurants.findOne()
{
	"_id" : ObjectId("55cba2476c522cafdb053adf"),
	"location" : {
		"coordinates" : [
			-73.98241999999999,
			40.579505
		],
		"type" : "Point"
	},
	"name" : "Riviera Caterer"
}

由于本教程使用2dsphere索引,因此location字段中的几何数据 必须遵循GeoJSON格式

查询2:圣诞节将至, 您休假到某著名旅游城市,经过这一年颇长的居家时光,您想好好享受这来之不易的假期, 午餐时间,您希望基于您当前所处的位置 通过某美食网提供移动应用查询就近饭店信息:

第一步:基于您当前的地理坐标 先计算出您所在的社区 (基于$geoIntersects 运算符):

var neighborhood = db.neighborhoods.findOne( { geometry: { $geoIntersects: { $geometry: { type: "Point", coordinates: [ -73.93414657, 40.82302903 ] } } } } )

第二步:根据您当前的所在社区 查询社区中包含的所有餐馆 (基于$geoWithin 运算符):

db.restaurants.find( { location: { $geoWithin: { $geometry: neighborhood.geometry } } } )
输出如下:
{ "_id" : ObjectId("55cba2476c522cafdb057f93"), "location" : { "coordinates" : [ -73.93781899999999, 40.8073089 ], "type" : "Point" }, "name" : "Perfect Taste" }
{ "_id" : ObjectId("55cba2476c522cafdb058b1a"), "location" : { "coordinates" : [ -73.943046, 40.807862 ], "type" : "Point" }, "name" : "Event Productions Catering & Food Services" }
{ "_id" : ObjectId("55cba2476c522cafdb054824"), "location" : { "coordinates" : [ -73.9446889, 40.8087276 ], "type" : "Point" }, "name" : "Sylvia'S Restaurant" }
{ "_id" : ObjectId("55cba2476c522cafdb0576a9"), "location" : { "coordinates" : [ -73.9450154, 40.808573 ], "type" : "Point" }, "name" : "Corner Social" }
{ "_id" : ObjectId("55cba2476c522cafdb0576c1"), "location" : { "coordinates" : [ -73.9449154, 40.8086991 ], "type" : "Point" }, "name" : "Cove Lounge" }
{ "_id" : ObjectId("55cba2476c522cafdb056a56"), "location" : { "coordinates" : [ -73.9508604, 40.8111432 ], "type" : "Point" }, "name" : "Manna'S Restaurant" }
{ "_id" : ObjectId("55cba2476c522cafdb0548db"), "location" : { "coordinates" : [ -73.9503386, 40.8116759 ], "type" : "Point" }, "name" : "Harlem Bar-B-Q" }
{ "_id" : ObjectId("55cba2476c522cafdb05488c"), "location" : { "coordinates" : [ -73.949938, 40.812365 ], "type" : "Point" }, "name" : "Hong Cheong" }
…

查询3: 上一步查询出非常多的餐厅,您已经有点眼花缭乱,有选择障碍了,怎奈饥肠辘辘,好吧来进一步来过滤离您最近的餐厅,以从近到远的排序顺序返回距离您1英里以内的所有餐厅 (使用$nearSphere和$maxDistance):

var METERS_PER_MILE = 1609.34
db.restaurants.find({ location: { $nearSphere: { $geometry: { type: "Point", coordinates: [ -73.93414657, 40.82302903 ] }, $maxDistance: 1 * METERS_PER_MILE } } })

输出如下:

{ "_id" : ObjectId("55cba2476c522cafdb058c83"), "location" : { "coordinates" : [ -73.9316894, 40.8231974 ], "type" : "Point" }, "name" : "Gotham Stadium Tennis Center Cafe" }
{ "_id" : ObjectId("55cba2476c522cafdb05864b"), "location" : { "coordinates" : [ -73.9378967, 40.823448 ], "type" : "Point" }, "name" : "Tia Melli'S Latin Kitchen" }
{ "_id" : ObjectId("55cba2476c522cafdb058c63"), "location" : { "coordinates" : [ -73.9303724, 40.8234978 ], "type" : "Point" }, "name" : "Chuck E. Cheese'S" }
{ "_id" : ObjectId("55cba2476c522cafdb0550aa"), "location" : { "coordinates" : [ -73.93795159999999, 40.823376 ], "type" : "Point" }, "name" : "Domino'S Pizza" }
{ "_id" : ObjectId("55cba2476c522cafdb0548e0"), "location" : { "coordinates" : [ -73.9381738, 40.8224212 ], "type" : "Point" }, "name" : "Red Star Chinese Restaurant" }
…

查询4: 哇还是很多餐厅,再来个精细化查询吧,查询餐厅与您所在位置的距离(单位:公里) 按照最接近到最远排序。此命令的输出如下所示 (使用$geoNear), 执行此类查询,您可以使用$geoNear计算一组点(餐厅位置)与另一个点(您当前所在位置)的距离。您还可以添加distanceMultiplier以千米为单位测量距离:

db.restaurants.aggregate([ { "$geoNear":{ "near":{ "type":"Point", "coordinates":[ -73.93414657, 40.82302903 ] }, "spherical":true, "distanceField":"DistanceKilometers", "distanceMultiplier":0.001 } } ])

输出如下:

{ "_id" : ObjectId("55cba2476c522cafdb0548e0"), "location" : { "coordinates" : [ -73.9381738, 40.8224212 ], "type" : "Point" }, "name" : "Red Star Chinese Restaurant", "DistanceKilometers" : 0.34593150407552176 }
{ "_id" : ObjectId("55cba2476c522cafdb0578b3"), "location" : { "coordinates" : [ -73.93011659999999, 40.8219403 ], "type" : "Point" }, "name" : "Marisco Centro Seafood Restaurant  & Bar", "DistanceKilometers" : 0.3604668364179945 }
{ "_id" : ObjectId("55cba2476c522cafdb056b6a"), "location" : { "coordinates" : [ -73.93011659999999, 40.8219403 ], "type" : "Point" }, "name" : "Applebee'S Neighborhood Grill & Bar", "DistanceKilometers" : 0.3604668364179945 }
…

最终终于通过某美食网提供的移动应用查询,吃上美食大餐! 感谢高科技!!! 寒冷圣诞季,坐在暖绒绒的餐厅,吃着带着极具地方特色的菜肴,真香!

总结

通过本博文介绍示例的餐馆地理信息导入、存储、2dsphere索引创建和查询,希望您已经深入了解了Amazon DocumentDB的地理空间查询功能,并能根据您的应用需求, 采用DocumentDB云原生文档数据库来构建您的应用:例如内容和目录管理、配置文件管理、移动和Web应用程序等,通过使用DocumentDB托管的云原生文档数据库,能将您从繁重无差别的低价值运维工作中解放出来,将更多的精力投入到高价值的应用开发和创新中,同时能使用DocumentDB来存储、索引和查询地理信息,轻松构建您的地理信息查询应用,玩转DocumentDB地理空间!

本篇作者

Bingbing liu

AWS数据库解决方案架构师,负责基于AWS的数据库解决方案的咨询与架构设计,同时致力于大数据方面的研究和推广。在加入AWS 之前曾在Oracle工作多年,在数据库云规划、设计运维调优、DR解决方案、大数据和数仓以及企业应用等方面有丰富的经验。