Map POI aggregation function practice

introduce

When implementing map-related business functions, we are often faced with the need to display different forms of POI data of map points of interest at different levels of magnitude, focusing on regional statistical values ​​on a large geographic scope, and focusing on a small geographic scope. The details and operations of POI, so it is inevitable to use the aggregation function of map POI. Next, let's discuss how the POI of the two-dimensional plane map is aggregated.

Implementation plan

The aggregation function implemented in this paper uses the grid centroid algorithm as the point aggregation strategy, and the quad tree is used for fast search. The implementation steps are as follows: 1. Divide the current view area of ​​the map into several grids, each grid is a rectangle of the same size;Image.png

2. Traverse all grids, and do the following for each grid: find all POIs in each grid, calculate the centroid (x, y) of POI points, where x is the average value of the x-axis of all POIs, y For the y-axis average of all POIs, use the centroid point as the aggregation point to replace the original POI;Image [2].png

Image [3].png

3. Render these aggregation points and adjust the appearance according to the number of POIs aggregated;

Image [4].png

4. If the map view changes (zoom, move, resize), repeat step 1.

Image [5].png

The difficulty of this scheme is how to quickly find all POIs within each grid range. Of course, we can use the most mechanical method, that is, traverse each point to determine whether it is within a certain grid range that has been delineated, but the execution time of this method will increase exponentially with the increase of the number of grids and POIs , is not suitable for handling massive POIs. Therefore, it is necessary to construct a quadtree to provide fast search capability.

Construct quadtree

We often use binary trees to find and store one-dimensional data. For the map POI (with xy coordinates), it is a two-dimensional array, which can be stored and searched using a quadtree.

Features of quadtree nodes:

  1. The attributes that each tree node needs to contain are as follows:
constructor(data, conf) {
    // 每个节点所指定的地址范围 xmin ymin xmax ymax
    this.extent =  new Extent(conf.extent)
    // 节点数据容量,超过该容量则会分裂成4个子节点
    this.bucketLimit = conf.bucketLimit || 10
    // 当前节点的深度,根节点深度为0
    this.deep = conf.deep || 0
    //存储的POI
    this.points = data
  }
复制代码
  1. Each node can be split into 4 child nodes. The extent of these four child nodes is the 4 geographic quadrants of the parent node, namely northWest (northwest) northEast (northeast) southWest (southwest) southEast (southeast)

  2. If the number of POI points contained in the current node range exceeds the capacity bucketLimit, and the node depth does not exceed the maximum depth, the child nodes will be split.

4. All POI points will eventually be placed in leaf nodes

Image [6].png

The picture shows the quadtree constructed based on the existing data. It can be seen from the picture that the more frequently the nodes are split, the denser the POI

Find POIs within a grid

构造好了四叉树,就可以用它来查找单个网格Grid内的POI点,这是一个对四叉树递归遍历的过程,操作如下:

  1. 从根节点开始,判断当前节点extent是否与网格Grid几何相交,如果相交,则进入步骤2

  2. 当前节点有POI点(只有叶子节点有POI点),则遍历这些POI点,将处于Grid范围内的POI点存入数组Arr;否则进入步骤3

  3. 如果当前节点有子节点(如果有的话必定是4个子节点),则按步骤1处理这些子节点

最终我们会得到一个Grid范围内所有POI点的数组Arr。每个网格范围内的POI点到手了,直接求取它们xy轴的平均值,就能得到这个网格质心聚合点的坐标,接下来就是在地图上的渲染工作。

Honeycam 2022-04-11 16-29-40.gif

代码层实现四叉树

我依据代码复用情况建了两个类

1.四叉树QuadTreeNode,提供树的构建和查找功能 findPoints(extent)
2.矩形范围Extent,这里的范围即有四叉树的地理范围,也有网格的地理范围,用于计算点和面,面和面之间的几何关系

QuadTreeNode

  /**
   * 查找指定范围内的坐标点
   * @param extent 指定范围
   * @returns {Array}
   */
  findPoints(extent) {

    if (!(extent instanceof Extent)) {
      extent = new Extent(extent)
    }

    let arr = []

    function travel(node) {

      if (node.isIntersects(extent)) {

        //如果当前四叉树节点有points,则将在extent范围内的points入栈
        if (node.points.length > 0) {
          node.points.forEach(point => {
            if (extent.within(point)) {
              arr.push(point)
            }
          })

        } else {
          const {northWest, northEast, southWest, southEast} = node

          if (northWest && northWest.isIntersects(extent)) {
            travel(northWest)
          }
          if (northEast && northEast.isIntersects(extent)) {
            travel(northEast)
          }
          if (southWest && southWest.isIntersects(extent)) {
            travel(southWest)
          }
          if (southEast && southEast.isIntersects(extent)) {
            travel(southEast)
          }
        }
      }
    }

    travel(this)

    return arr
  }
  
}
复制代码

Extent

class  Extent{
  constructor(conf = {xmin: 0, ymin: 0, xmax: 100, ymax: 100}){

    this.xmin = conf.xmin
    this.ymin = conf.ymin
    this.xmax = conf.xmax
    this.ymax = conf.ymax
  }
  /**
   * 判断当前范围是否与指定范围相交
   * 如果两个矩形相交,则两个矩形中心点间的距离肯定小于两个矩形边长和的1/2
   * @param extent 指定范围
   * @returns {boolean}
   */
  intersects(extent) {

    const {xmin, ymin, xmax, ymax} = extent

    //x轴 两个矩形中心点距离 * 2
    let lx = Math.abs(this.xmax + this.xmin - xmin - xmax)
    //x轴 两个矩形边长和
    let bx = Math.abs(this.xmax - this.xmin + xmax - xmin)
    //y轴 两个矩形中心点距离 * 2
    let ly = Math.abs(this.ymax + this.ymin - ymin - ymax)
    //y轴 两个矩形x轴边长和
    let by = Math.abs(this.ymax - this.ymin + ymax - ymin)

    if (lx <= bx && ly <= by) {
      return true
    } else {
      return false
    }
  }
}
复制代码

聚合功能的应用

1.解决重叠坐标点的选择问题 这是我非常想解决的问题,在实际项目中遇到的真实数据其实没有那么理想,很多情况下有部分POI数据因为地址不够详细或者定位api出错会导致坐标点一模一样,从而导致一堆点叠加在某个位置,鼠标操作只能取到最上方点的属性。既然实现了点聚合功能,那么可以配置缩小聚合的阈值,提取到所有叠加点的属性。

2.为热力图层的渲染提供基础数据 热力图其实可以看做点聚合的另一种展示方式,它所需要的源数据与点聚合非常类似(x坐标,y坐标,点的权值)。有了源数据,只要将做表单做一次地理坐标和平面坐标的转换,权值归一化,再渲染出热力图也是很简单的事。

3.海量数据的统计展示 还是文章开头提到的需求,大比例尺我要看POI的聚合数量,小比例尺我要看单个POI的属性明细。

优点和缺陷

  1. 四叉树其实是个空间换时间的做法,和质心合并算法(求平均值)结合带来的有点就是计算速度快
  2. There is a certain error in the grid algorithm. If there is a bunch of points cut on the boundary line of the two grids, the algorithm will cut the bunch of points into two aggregate points, but this is not a big deal.

Article example

Map POI Aggregation Demo Page

GitHub source code

Reference article

"Exploration and Practice of Map Interest Point Aggregation Algorithms" The algorithm solutions mentioned in this article are more detailed and vivid, suitable for more in-depth research

Guess you like

Origin juejin.im/post/7085274646949396487