正六边形空间聚类
半野

1.背景

在平面镶嵌中,用同一种正多边形镶嵌只有正三角形、正方形以及正六边形能够实现。于大于六边的正多边形,目前还有找到镶嵌平面的方式。当一个正多边形的边数越多,其越接近于圆,因此使用正六边形来进行进行平面镶嵌会比正三角形、正方形好看。

本文将会介绍如何基于使用正六边形统计区域内点的个数。本文代码使用为JavaScript,提取自我基于 Openlayers 写的脚本模块。

2.难点

对于填充的正多边形,其都有一个唯一的行列号,这个行列号是要用来判断这个这个正六边形是否已经绘制过了,避免重复绘制。正六边形镶嵌不同于正正三角形,因为其镶嵌并不是规整的行列。如图1所示,如果我们用行列号对其进行编号,对于正三角形和正方形,其上下行的列号是一致的,但是对于正六边形而言,其编号是呈现折线形式的,加之变数又多,因此其在绘制的时候对于六边形位置的计算要更为复杂——正方形的中心在行列上都在一条线上,最简单;三角形中心在列上是一条直线,只是在行上交错,但是其行列号是呈现一条直线的。

image

图 1 正多边形平面镶嵌方式

3.行高

使用正六边形填充平面区域时,其每一行中心在一条线上。正六边形填充区域的行高并不等于2R——两倍的外接圆半径R,如图2所示,其行高等于1.5倍的R—— 1.5 * R * sin(30°)。

1
`//radius:外接圆半径 //rowH:行高 const rowH = radius + radius >> 1;`

image

图 2 正六边形行高

4.XY坐标换算正六边形行列号

对于所有的点,我们可以找到一个最大、最小XY,即MBR——[minX, minY, maxX, maxY],我们使用图1所示的六边形填充方式进行填充,这样其每一行如图3中黄色线所示的区域,这样绘制,只有位于每一行上1/3区域的点是存在有争议:可能属于上一行,也可能属于下一行。这里坐标系Y轴向上、X轴向右,行列号从0开始编号。

4.1.行号

因此,首先根据Y坐标计算其可能位于的行号、当前行 bottom 的Y坐标、当前行争议区域 bottom 的Y坐标:

1
`//ptY:点 Y坐标 let mayRow = Math.floor((ptY - minY) / rowH); //curRowBottom:当前行的 bottom 坐标 const curRowBottom = mayRow * rowH + miny; //disputeBottom:争议区域 bottom 坐标 const disputeBottom = curRowBottom + radius;`

接下来需要根据点的 Y 坐标判断其是否处于争议区域,如果在争议区域需要进一步判断其所在的行号。

无论是奇数行、偶数行,其每个列宽—— 2 * R * Math.sin(Math.PI / 3)——上部争议区域都存在有一个三角形(如图3),如果点在这个三角形内,其行数不变。

点在凸多边形内判断,只需要计算点与多边形各个点之间的角度和是否为360°即可。

4.2.列号

列号的计算与行号的奇偶有关系,奇数行点 X 坐标需要向右偏移半个列宽:

1
`//ptX:点 X 坐标 //halfW:半个列宽 let pxPy = ((mayRow & 1) === 0) ? ptX:点 X 坐标 : (ptX + halfW); //colW:列宽 let mayCol = Math.floor((pxPy - minX) / colW)`

image

图 3 行高示意

5.根据行列号计算六边形坐标

通过行列号,我们可以直接得出图4中1号点的坐标:x- col * colW + minX - ((row & 1) === 0 ? 0 : halfColW)y- row * rowH。其余点的坐标则可以通过其外接圆半径R和1号点的坐标[x1, y1]换算得到:

1.
[ col * colW + minX, row * rowH + minY ]

2.
[ x1 + R * cos(30°), y1 - R * sin(30°) ]

3.
[ x1 + R * cos(30°) * 2, y1 ]

4.
[ x1 + R * cos(30°) * 2, y1 + R ]

5.
[ x1 + R * cos(30°), y1 + R + R * sin(30°) ]

6.
[ x1, y1 + R ]

这里利用了正六边形边长与外接圆半径大小相同的规律。

image

图 4 六边形点位序号

6.六边形缓存与绘制

在5中,我们已经可以根据一个点绘制出其所在的六边形了。但是,每个六边形内可能包含多个点,为了避免六边形的重复绘制与统计,我们需要缓存这个六边形是否已经绘制了,这里就需要用到我们的行列号了。每个六边形都具有一个独一无二的行列号,我们可以通过简单的将其进行字符串拼接,例如:row+'-'+col,作为一个字典的key来判断这个六边形是否已经存在,字典的 value 可以用 点数组来表示。

7.OlHoneycomb

基于Openlayers的正六边形格网聚类已经被封装好了,类似cluster,作为一个VectorSource,Github地址:https://github.com/zxyao145/OlHoneycomb ,npm地址:https://www.npmjs.com/package/olhoneycomb

由 Hexo 驱动 & 主题 Keep
总字数 105.7k 访客数 访问量