maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
110 lines (94 loc) • 4.25 kB
text/typescript
import type {CollisionBoxArray} from '../data/array_types.g';
import Point from '@mapbox/point-geometry';
import type Anchor from './anchor';
import {SymbolPadding} from '../style/style_layer/symbol_style_layer';
/**
* A CollisionFeature represents the area of the tile covered by a single label.
* It is used with CollisionIndex to check if the label overlaps with any
* previous labels. A CollisionFeature is mostly just a set of CollisionBox
* objects.
*
* @private
*/
class CollisionFeature {
boxStartIndex: number;
boxEndIndex: number;
circleDiameter: number;
/**
* Create a CollisionFeature, adding its collision box data to the given collisionBoxArray in the process.
* For line aligned labels a collision circle diameter is computed instead.
*
* @param anchor The point along the line around which the label is anchored.
* @param shaped The text or icon shaping results.
* @param boxScale A magic number used to convert from glyph metrics units to geometry units.
* @param padding The amount of padding to add around the label edges.
* @param alignLine Whether the label is aligned with the line or the viewport.
* @private
*/
constructor(collisionBoxArray: CollisionBoxArray,
anchor: Anchor,
featureIndex: number,
sourceLayerIndex: number,
bucketIndex: number,
shaped: any,
boxScale: number,
padding: SymbolPadding,
alignLine: boolean,
rotate: number) {
this.boxStartIndex = collisionBoxArray.length;
if (alignLine) {
// Compute height of the shape in glyph metrics and apply collision padding.
// Note that the pixel based 'text-padding' is applied at runtime
let top = shaped.top;
let bottom = shaped.bottom;
const collisionPadding = shaped.collisionPadding;
if (collisionPadding) {
top -= collisionPadding[1];
bottom += collisionPadding[3];
}
let height = bottom - top;
if (height > 0) {
// set minimum box height to avoid very many small labels
height = Math.max(10, height);
this.circleDiameter = height;
}
} else {
// margin is in CSS order: [top, right, bottom, left]
let y1 = shaped.top * boxScale - padding[0];
let y2 = shaped.bottom * boxScale + padding[2];
let x1 = shaped.left * boxScale - padding[3];
let x2 = shaped.right * boxScale + padding[1];
const collisionPadding = shaped.collisionPadding;
if (collisionPadding) {
x1 -= collisionPadding[0] * boxScale;
y1 -= collisionPadding[1] * boxScale;
x2 += collisionPadding[2] * boxScale;
y2 += collisionPadding[3] * boxScale;
}
if (rotate) {
// Account for *-rotate in point collision boxes
// See https://github.com/mapbox/mapbox-gl-js/issues/6075
// Doesn't account for icon-text-fit
const tl = new Point(x1, y1);
const tr = new Point(x2, y1);
const bl = new Point(x1, y2);
const br = new Point(x2, y2);
const rotateRadians = rotate * Math.PI / 180;
tl._rotate(rotateRadians);
tr._rotate(rotateRadians);
bl._rotate(rotateRadians);
br._rotate(rotateRadians);
// Collision features require an "on-axis" geometry,
// so take the envelope of the rotated geometry
// (may be quite large for wide labels rotated 45 degrees)
x1 = Math.min(tl.x, tr.x, bl.x, br.x);
x2 = Math.max(tl.x, tr.x, bl.x, br.x);
y1 = Math.min(tl.y, tr.y, bl.y, br.y);
y2 = Math.max(tl.y, tr.y, bl.y, br.y);
}
collisionBoxArray.emplaceBack(anchor.x, anchor.y, x1, y1, x2, y2, featureIndex, sourceLayerIndex, bucketIndex);
}
this.boxEndIndex = collisionBoxArray.length;
}
}
export default CollisionFeature;