maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
115 lines (99 loc) • 4.67 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';
import {applyTextFit} from './shaping';
/**
* 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.
*/
export 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.
*/
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 {
const icon = shaped.image?.content && (shaped.image.textFitWidth || shaped.image.textFitHeight) ?
applyTextFit(shaped) :
{
x1: shaped.left,
y1: shaped.top,
x2: shaped.right,
y2: shaped.bottom
};
// margin is in CSS order: [top, right, bottom, left]
icon.y1 = icon.y1 * boxScale - padding[0];
icon.y2 = icon.y2 * boxScale + padding[2];
icon.x1 = icon.x1 * boxScale - padding[3];
icon.x2 = icon.x2 * boxScale + padding[1];
const collisionPadding = shaped.collisionPadding;
if (collisionPadding) {
icon.x1 -= collisionPadding[0] * boxScale;
icon.y1 -= collisionPadding[1] * boxScale;
icon.x2 += collisionPadding[2] * boxScale;
icon.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(icon.x1, icon.y1);
const tr = new Point(icon.x2, icon.y1);
const bl = new Point(icon.x1, icon.y2);
const br = new Point(icon.x2, icon.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)
icon.x1 = Math.min(tl.x, tr.x, bl.x, br.x);
icon.x2 = Math.max(tl.x, tr.x, bl.x, br.x);
icon.y1 = Math.min(tl.y, tr.y, bl.y, br.y);
icon.y2 = Math.max(tl.y, tr.y, bl.y, br.y);
}
collisionBoxArray.emplaceBack(anchor.x, anchor.y, icon.x1, icon.y1, icon.x2, icon.y2, featureIndex, sourceLayerIndex, bucketIndex);
}
this.boxEndIndex = collisionBoxArray.length;
}
}