在mapboxgl里面,symbol的碰撞检测是通过计算symbol的二维边界框,然后设置顶点透明度来实现symbol淡入淡出效果来模拟碰撞。
GridIndex
是一个二维空间数据结构,使用一个 2D 平面作为“视口”——即设备屏幕的平面。
碰撞检测算法的输出用于将每个symbol的目标不透明度设置为 0 或 1。每次通过碰撞检测更新不透明度时,“时钟参数”实质上被重置为零,CPU 重新计算基线“当前不透明度”。将其概念化的一种方法是“如果当前不透明度与目标不透明度不同,则正在进行淡入淡出动画”。
style._updatePlacement
中会进行style.placement.updateLayerOpacities
来更新opacity,赋值的地方在updateBucketOpacities()
方法中,
this.placements
里面存储了每个symbol碰撞检测后的结果,例如:
{icon: false,skipFade: false,text: true}
然后根据placements
的结果去创建JointOpacityState。
通过placedGlyphBoxes
的结果来判断是否渲染,在placeCollisionBox()
方法中计算在屏幕空间的矩形位置(tlX, tlY, brX, brY)
,判断是否在grid范围里,如果在就可以绘制。
创建CollisionFeature
实例来构成每个碰撞体,因为要算里面的 anchorX/Y/Z;
在mapboxgl里,在线程里添加symbol的时候,会有个anchor的局部瓦片锚点坐标,如果坐标超出瓦片8192就不会绘制,同时如果是球面模式下,还会对锚点坐标进行投影,具体算法就是根据锚点的局部瓦片坐标,通过tileCoordToECEF()
转换成ECEF三维坐标pos
(坐标单位是像素),然后再根据当前瓦片的zoom来计算ECEF坐标下的包围盒,根据该包围盒可以计算出ECEF坐标=>球面瓦片相对坐标的变换矩阵normalizationMatrix
,将pos
与normalizationMatrix
相乘即可得到投影后的锚点坐标。这个过程完成的是一个从平面的anchor坐标转变成球面的anchor坐标的过程。
tileCoordToECEF()
方法里其实是先把锚点的局部瓦片坐标转成经纬度坐标了,然后再转成ECEF坐标,这里与cesium不同的是,地球半径是个常量GLOBE_RADIUS = 1303.7972938088067 = EXTENT / Math.PI / 2.0
(该半径的意思就是在0级瓦片下的球半径的像素数,所以单位不是米,是像素),所以算出来的世界坐标是基于像素的。
worldSize计算,可见0级时候就是512,随缩放呈指数变大
scale = Math.pow(2, zoom);
worldSize = tileSzie * scale; // tileSzie = 512
CollisionIndex
用于防止symbol重叠的碰撞索引。它跟踪之前的symbol放置的位置,并用于检查新的symbol是否与之前添加的symbol重叠。插入有两个步骤:首先placeCollisionBox/Circles检查是否有空间放置symbol,然后insertCollisionBox/Circles实际将符号放入索引中。两步过程允许成对的符号插入在一起,即使它们重叠。
每个CollisionIndex
的范围就是整个屏幕大小,然后再加上一个常量viewportPadding
。当一个symbol越过引起它被包含在碰撞检测中的边缘时,它将导致它周围的symbol发生变化。viewportPadding
这个值就指定了在视口边缘填充多少像素以进行碰撞检测,以便大量的更改发生在屏幕外。增大这个常数可以提高标签的稳定性,但代价很高。
textPixelRatio = tile.tileSize / EXTENT;
textPixelRatio
是个定值。
CollisionFeature
表示单个label覆盖的瓦片区域。它与CollisionIndex
一起使用,以检查标签是否与任何先前的标签重叠。一个CollisionFeature
主要是一组CollisionBox
对象。
主要与projectedPoint有关,collisionBox.x1 * tileToViewport
值已经很小了,它是字体的位置坐标,所以重点还是在锚点坐标上,看下怎么计算锚点坐标到当前屏幕坐标的映射。
const tlX = collisionBox.x1 * tileToViewport + projectedPoint.point.x;
const tlY = collisionBox.y1 * tileToViewport + projectedPoint.point.y;
const brX = collisionBox.x2 * tileToViewport + projectedPoint.point.x;
const brY = collisionBox.y2 * tileToViewport + projectedPoint.point.y;