Spaces:
Sleeping
Sleeping
File size: 6,253 Bytes
b593f0b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | // @flow
import {number as interpolate} from '../style-spec/util/interpolate';
import Anchor from '../symbol/anchor';
import checkMaxAngle from './check_max_angle';
import type Point from '@mapbox/point-geometry';
import type {Shaping, PositionedIcon} from './shaping';
export {getAnchors, getCenterAnchor};
function getLineLength(line: Array<Point>): number {
let lineLength = 0;
for (let k = 0; k < line.length - 1; k++) {
lineLength += line[k].dist(line[k + 1]);
}
return lineLength;
}
function getAngleWindowSize(shapedText: ?Shaping,
glyphSize: number,
boxScale: number): number {
return shapedText ?
3 / 5 * glyphSize * boxScale :
0;
}
function getShapedLabelLength(shapedText: ?Shaping, shapedIcon: ?PositionedIcon): number {
return Math.max(
shapedText ? shapedText.right - shapedText.left : 0,
shapedIcon ? shapedIcon.right - shapedIcon.left : 0);
}
function getCenterAnchor(line: Array<Point>,
maxAngle: number,
shapedText: ?Shaping,
shapedIcon: ?PositionedIcon,
glyphSize: number,
boxScale: number) {
const angleWindowSize = getAngleWindowSize(shapedText, glyphSize, boxScale);
const labelLength = getShapedLabelLength(shapedText, shapedIcon) * boxScale;
let prevDistance = 0;
const centerDistance = getLineLength(line) / 2;
for (let i = 0; i < line.length - 1; i++) {
const a = line[i],
b = line[i + 1];
const segmentDistance = a.dist(b);
if (prevDistance + segmentDistance > centerDistance) {
// The center is on this segment
const t = (centerDistance - prevDistance) / segmentDistance,
x = interpolate(a.x, b.x, t),
y = interpolate(a.y, b.y, t);
const anchor = new Anchor(x, y, b.angleTo(a), i);
anchor._round();
if (!angleWindowSize || checkMaxAngle(line, anchor, labelLength, angleWindowSize, maxAngle)) {
return anchor;
} else {
return;
}
}
prevDistance += segmentDistance;
}
}
function getAnchors(line: Array<Point>,
spacing: number,
maxAngle: number,
shapedText: ?Shaping,
shapedIcon: ?PositionedIcon,
glyphSize: number,
boxScale: number,
overscaling: number,
tileExtent: number) {
// Resample a line to get anchor points for labels and check that each
// potential label passes text-max-angle check and has enough froom to fit
// on the line.
const angleWindowSize = getAngleWindowSize(shapedText, glyphSize, boxScale);
const shapedLabelLength = getShapedLabelLength(shapedText, shapedIcon);
const labelLength = shapedLabelLength * boxScale;
// Is the line continued from outside the tile boundary?
const isLineContinued = line[0].x === 0 || line[0].x === tileExtent || line[0].y === 0 || line[0].y === tileExtent;
// Is the label long, relative to the spacing?
// If so, adjust the spacing so there is always a minimum space of `spacing / 4` between label edges.
if (spacing - labelLength < spacing / 4) {
spacing = labelLength + spacing / 4;
}
// Offset the first anchor by:
// Either half the label length plus a fixed extra offset if the line is not continued
// Or half the spacing if the line is continued.
// For non-continued lines, add a bit of fixed extra offset to avoid collisions at T intersections.
const fixedExtraOffset = glyphSize * 2;
const offset = !isLineContinued ?
((shapedLabelLength / 2 + fixedExtraOffset) * boxScale * overscaling) % spacing :
(spacing / 2 * overscaling) % spacing;
return resample(line, offset, spacing, angleWindowSize, maxAngle, labelLength, isLineContinued, false, tileExtent);
}
function resample(line, offset, spacing, angleWindowSize, maxAngle, labelLength, isLineContinued, placeAtMiddle, tileExtent) {
const halfLabelLength = labelLength / 2;
const lineLength = getLineLength(line);
let distance = 0,
markedDistance = offset - spacing;
let anchors = [];
for (let i = 0; i < line.length - 1; i++) {
const a = line[i],
b = line[i + 1];
const segmentDist = a.dist(b),
angle = b.angleTo(a);
while (markedDistance + spacing < distance + segmentDist) {
markedDistance += spacing;
const t = (markedDistance - distance) / segmentDist,
x = interpolate(a.x, b.x, t),
y = interpolate(a.y, b.y, t);
// Check that the point is within the tile boundaries and that
// the label would fit before the beginning and end of the line
// if placed at this point.
if (x >= 0 && x < tileExtent && y >= 0 && y < tileExtent &&
markedDistance - halfLabelLength >= 0 &&
markedDistance + halfLabelLength <= lineLength) {
const anchor = new Anchor(x, y, angle, i);
anchor._round();
if (!angleWindowSize || checkMaxAngle(line, anchor, labelLength, angleWindowSize, maxAngle)) {
anchors.push(anchor);
}
}
}
distance += segmentDist;
}
if (!placeAtMiddle && !anchors.length && !isLineContinued) {
// The first attempt at finding anchors at which labels can be placed failed.
// Try again, but this time just try placing one anchor at the middle of the line.
// This has the most effect for short lines in overscaled tiles, since the
// initial offset used in overscaled tiles is calculated to align labels with positions in
// parent tiles instead of placing the label as close to the beginning as possible.
anchors = resample(line, distance / 2, spacing, angleWindowSize, maxAngle, labelLength, isLineContinued, true, tileExtent);
}
return anchors;
}
|