百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

如何有效地在地图上搜索兴趣点 (POI) 标记(译文-来自Booking)

toyiye 2024-06-21 12:08 10 浏览 0 评论

在 Booking.com,我们热衷于通过提供最佳的住宿搜索功能让用户的生活更轻松。我们希望我们的用户拥有选择最佳住宿的所有信息。众所周知,酒店的位置是选择住宿时最重要的标准之一,因为它是旅行体验的重要组成部分。

Booking.com 地图功能是一个强大的工具,因为它以非常直观的方式提供位置信息。只需几秒钟,用户就可以确定该物业是否位于他们的首选位置。但是,通常仅查看属性本身的位置是不够的。显示旅途中最有趣的景点的位置也很重要。他们有多远?从酒店找到他们有多容易?

为了帮助我们的用户回答这些问题,除了属性标记之外,我们还显示所谓的兴趣点 (POI) 标记。顾名思义,这些标记突出了旅行者感兴趣的地方,例如海滩、滑雪胜地、地标以及城市和机场,所有这些都可以让用户更好地了解酒店的位置。


地图上的城市、景点和机场标记


如何在地图上搜索?

当用户打开地图时,通常会看到一个称为边界框 (BBox) 的特定区域。从技术上讲,BBox 是一个矩形,其角由 4 个地理点表示:

  • 西北(NW);
  • 东北(NE);
  • 东南(SE);
  • 西南 (SW)。

因为它是一个矩形,所以只知道彼此成对角线的 2 个地理点就足以派生出一个 BBox。例如东北和西南。



边界框——可见的地图区域。

从逻辑上讲,仅显示此 BBox 内的那些 POI 标记就足够了,因为用户看不到世界其他地方。因此,我们可以在搜索地图内容时使用 BBox 作为搜索关键字。

用户可能会通过平移和缩放地图来更改可见区域,并且此类交互的数量可能相对较高。我们想要提供最好的用户体验,我们想要尽快提供在地图上显示的信息,这将需要一个高效的搜索机制,可以与 BBox 不寻常的搜索键一起工作。

说到key搜索,搜索树就是一个经典的解决方案,当然本例也不例外!但首先我们需要找到一种利用这种树的方法,并弄清楚如何将 BBox 用作键。为了解决这个问题,我们将使用四叉树。

四叉树

四叉树是一种搜索树,其中每个内部节点恰好有四个子节点。你问为什么是四个?具有两个子节点的简单搜索树通常在每一层将搜索集一分为二。地图是由纬度和经度组成的二维结构,在每一层上我们需要将每个维度分成两块,从而产生四个象限。这棵树的每个节点也是一个 BBox。根节点将是包含整个世界的 BBox,每个子节点将是父 BBox 的一个象限。


用四叉树搜索。


由于我们的主要目标是找到要在地图上显示的标记,因此树的每个节点也将包含一些(通常相对较少)数量的标记。最简单的四叉树节点的代码表示如下所示:

类 QuadTreeNode { 
   private Set<Marker> 标记;
   私有边界框 bbox;
   私人QuadTreeNode[] 孩子;// 大小始终为 4 
   private  final  int maxEntryCount; 

   ... 
}

搜索

让我们看看我们如何使用四叉树来查找,例如,包含在 BBox 中的五个地标。假设我们树的每个节点都包含 10 个地标,它们位于代表当前节点的 BBox 内。我们应用 BFS,并且像往常一样,我们从根节点开始:

  1. 遍历此节点中的所有标记,并检查是否有任何标记与作为搜索键的 BBox 相交。知道地标的坐标非常简单,我们只需要检查这些坐标是否在 BBox 矩形内。如果条件
    (SW lat <= Marker lat <= NE lat) && (SW lon <= Marker lon <= NE lon)
    为真,则标记位于 BBox 内。我们将 BBox 边缘上的标记视为相交。
  2. 保存相交的标记。
  3. 如果我们找到五个地标,我们的搜索就结束了。
  4. 如果我们需要找到更多地标,我们检查哪些子节点与给定的搜索键 BBox 相交,并将它们保存在队列中以供进一步检查。它可能是一到四个节点。
  5. 从队列中选择下一个节点并转到步骤 1。

在代码中我们可以这样表达搜索过程:

类 QuadTree {
       私有QuadTreeNode 根;

       ... 
  
       public Set<Marker> findMarkers (BoundingBox bbox, int n) { 
        Set<Marker> foundEntries = new  HashSet <>(); 
        Queue<QuadTreeNode> nextNodesToCheck = new  LinkedList <>(); 
        nextNodesToCheck.add(root); 

        while (foundEntries.size() < n && !nextNodesToCheck.isEmpty()) { 
            int  stillToFind  = n - foundEntries.size(); 
            四叉树节点 nodeToCheck  = nextNodesToCheck.poll();

            列表<标记> currentEntries = nodeToCheck.getIntersectingEntries(bbox, stillToFind); 
            foundEntries.addAll(currentEntries); 

            如果(foundEntries.size() < n) { 
                nextNodesToCheck.addAll( 
                  nodeToCheck.getIntersectingChildren(bbox) 
                ); 
            } 
        }

        返回找到的条目;
    } 
}

当我们搜索 X 标记时,我们从树的顶部到底部搜索。这意味着将最先找到距离根节点最近的标记。由于我们希望向用户展示与他们的搜索最相关的标记,因此我们也需要考虑标记的重要性。

最重要的标记将保留在根节点中,如果将地图比例减小到整个世界可见时的级别,您就会看到它们。当用户缩放地图时,他们将看到对可见区域最重要的标记,即使它们在较低的缩放级别可能不可见。

标记的重要性基于可以由业务需求定义的自定义标准。例如,我们可能会根据用户评论确定地标标记的重要性。

边缘案例

我们还应该提到一个有趣的边缘案例。想象一下简单的纸质世界地图是什么样子的:它是一个矩形,其侧边在右侧为 180 度经线,在左侧为 -180 度经线。所以想象一下,用户在这里搜索财产。


边界框与地图的“边缘”相交。


在这种情况下,BBox 的 SW 经度将大于 NE 经度。在检查 BBox 矩形内的标记时,我们需要考虑这种情况。例如:

  • 假设 BBox 坐标是SW (160, 60), NE (-160, 70). 这是一个有效的 BBox。
  • 带有坐标的标记(-162, 62)不会被视为与 BBox 相交,因为.SW lon > Marker lon

解决这个问题的一种方法是将我们的 BBox 拆分为 2 个 BBox(如果我们知道的话)SW lon > NE lon。我们可以将 BBox 一分为二,使用 -180/180 子午线作为分隔符:

  • BBox1:SW (160, 60), NE (180, 70)
  • BBox2:SW (-180, 60), NE (-160, 70)

我们的标记(-162, 62)现在将匹配 BBox2。


将 BBox 分成两部分。


四叉树大厦

幸运的是,搜索树不会凭空出现,所以这就是软件工程师仍然有工作的原因之一。让我们看看如何实现满足我们用例的四叉树。

一开始,我们的树只有一个根节点,它是整个世界,它不包含任何标记,它是空的。

为了用标记填充树,我们需要遍历我们拥有的所有标记并将每个标记插入树中。假设每个节点最多可以包含 10 个标记。然后,在每次插入时我们做:

  1. 将当前标记放入当前节点的标记集中。
  2. 如果标记的大小设置 <= 10,我们就完成了——标记找到了它的位置。迭代到下一个标记。
  3. 如果标记的大小设置 > 10,我们需要获取该节点最不重要的标记并将其推送给其中一个子节点。(记住:节点离根越近,节点中标记的重要性越高)。
  • 如果尚未创建当前节点的子节点,则通过将节点的 BBox 拆分为四个子 BBox 来创建它们。
  • 搜索与最低优先级标记相交的子 BBox。
  • 递归地将此标记放入子节点(即以最不重要的标记和子节点作为输入转到上面的步骤 1)。

为了确保我们可以找到节点的最不重要的标记,我们可以使用 PriorityQueue 作为标记的容器。该集合中的标记将按重要性排序,因此我们可以轻松找到最不重要的标记。这也意味着在标记查找中,我们将首先找到节点最重要的标记。

这是插入实现的样子:

class  QuadTreeNode { 
   // 将最低优先级标记保持在队列顶部
   private PriorityQueue<Marker> markers; 
   私有边界框 bbox;
   私人QuadTreeNode[] 孩子;// 大小始终为 4 
   private  final  int maxEntryCount; 

   ... 

   public  void  insert (Marker marker) { 
       markers.add(marker); 
       if (markers.size() > maxEntryCount) { 
           if (children == null ) { 
               children = initializeChildren(); // 创建 4 个四叉树节点
           } 
      
           Marker entryToPassToChild  = markers.poll(); // 获取最低优先级标记
           List<QuadTreeNode> intersectingChildren = 
               getIntersectingChildren(entryToPassToChild.getBoundingBox()); 

           // 标记可以在节点的边缘。
           // 由于边缘是多个节点之间的共享区域,
           // 标记可以属于多个节点。
           对于(QuadTreeNode intersectingChild : intersectingChildren) { 
               intersectingChild.insert(entryToPassToChild); 
           } 
      } 
   } 
     ... 
}

我们在标记搜索服务启动期间构建四叉树并将其保存在内存中,不时刷新它。幸运的是,新城市不会经常建立,机场、地标等也是如此。但是,标记的重要性可能会发生变化,因此建议定期刷新树。

结论

四叉树是一个相对简单且(同时)强大的概念,它完美地解决了 BBox 任务的标记搜索。我们还发现该解决方案具有高度可扩展性,因为我们应对不断增加的负载所需要做的就是水平扩展我们的服务。

例如,存储超过 300k 标记的四叉树,(取决于服务负载)99% 的查找时间不到 1 到 ~5.5 毫秒,这对我们的目的来说非常快。


存储至少 300k 标记的四叉树中的查找时间


感谢您阅读本文并祝您搜索愉快!

致谢感谢Gregory Zak的后期创意和技术编辑,以及Al-Jerreau Davids的精美插图。vids的精美插图。

作者:Igor Dotsenko

出处:https://medium.com/booking-com-development/how-to-search-point-of-interest-poi-markers-on-a-map-efficiently-b5f3f37a914a

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码