introduzir
Ao implementar funções de negócios relacionadas a mapas, muitas vezes nos deparamos com a necessidade de exibir diferentes formas de dados de POI de pontos de interesse do mapa em diferentes níveis de magnitude, focando em valores estatísticos regionais em um grande escopo geográfico e focando em um pequena abrangência geográfica.Os detalhes e operações do POI, por isso é inevitável usar a função de agregação do mapa POI. Em seguida, vamos discutir como o POI do mapa plano bidimensional é agregado.
Plano de implementação
A função de agregação implementada neste artigo usa o algoritmo de centroide de grade como estratégia de agregação de pontos, e a árvore quádrupla é usada para busca rápida. As etapas de implementação são as seguintes: 1. Divida a área de visualização atual do mapa em várias grades, cada grade é um retângulo do mesmo tamanho;
2. Percorra todas as grades e faça o seguinte para cada grade: encontre todos os POIs em cada grade, calcule o centróide (x, y) dos pontos POI, onde x é o valor médio do eixo x de todos os POIs, y Para a média do eixo y de todos os POIs, use o ponto do centroide como ponto de agregação para substituir o POI original;
3. Renderize esses pontos de agregação e ajuste a aparência de acordo com o número de POIs agregados;
4. Se a visualização do mapa mudar (zoom, mover, redimensionar), repita a etapa 1.
A dificuldade deste esquema é como encontrar rapidamente todos os POIs dentro de cada intervalo de grade. Claro, podemos usar o método mais mecânico, ou seja, percorrer cada ponto para determinar se ele está dentro de uma determinada faixa de grade que foi delineada, mas o tempo de execução desse método aumentará exponencialmente com o aumento do número de grades e POIs , não é adequado para lidar com POIs massivos. Portanto, é necessário construir uma quadtree para fornecer capacidade de busca rápida.
Construir quadtree
Muitas vezes usamos árvores binárias para encontrar e armazenar dados unidimensionais. Para o mapa POI (com coordenadas xy), é um array bidimensional, que pode ser armazenado e pesquisado usando uma quadtree.
Características dos nós quadtree:
- Os atributos que cada nó de árvore precisa conter são os seguintes:
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
}
复制代码
-
Cada nó pode ser dividido em 4 nós filhos. A extensão desses quatro nós filhos são os 4 quadrantes geográficos do nó pai, ou seja, noroeste (noroeste) nordeste (nordeste) sudoeste (sudoeste) sudeste (sudeste)
-
Se o número de pontos POI contidos no intervalo de nós atual exceder a capacidade bucketLimit e a profundidade do nó não exceder a profundidade máxima, os nós filhos serão divididos.
4. Todos os pontos POI serão eventualmente colocados em nós folha
A imagem mostra a quadtree construída com base nos dados existentes. Pode-se ver pela imagem que quanto mais frequentemente os nós são divididos, mais denso é o POI
Encontrar POIs dentro de uma grade
构造好了四叉树,就可以用它来查找单个网格Grid内的POI点,这是一个对四叉树递归遍历的过程,操作如下:
-
从根节点开始,判断当前节点extent是否与网格Grid几何相交,如果相交,则进入步骤2
-
当前节点有POI点(只有叶子节点有POI点),则遍历这些POI点,将处于Grid范围内的POI点存入数组Arr;否则进入步骤3
-
如果当前节点有子节点(如果有的话必定是4个子节点),则按步骤1处理这些子节点
最终我们会得到一个Grid范围内所有POI点的数组Arr。每个网格范围内的POI点到手了,直接求取它们xy轴的平均值,就能得到这个网格质心聚合点的坐标,接下来就是在地图上的渲染工作。
代码层实现四叉树
我依据代码复用情况建了两个类
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的属性明细。
优点和缺陷
- 四叉树其实是个空间换时间的做法,和质心合并算法(求平均值)结合带来的有点就是计算速度快
- Há um certo erro no algoritmo da grade, se houver vários pontos cortados na linha de fronteira das duas grades, o algoritmo cortará o grupo de pontos em dois pontos agregados, mas isso não é grande coisa.
Exemplo de artigo
Página de demonstração de agregação de POI do mapa
Artigo de referência
"Exploração e Prática de Algoritmos de Agregação de Pontos de Interesse do Mapa" As soluções de algoritmo mencionadas neste artigo são mais detalhadas e vívidas, adequadas para pesquisas mais aprofundadas