1 | #include <mbgl/text/get_anchors.hpp> |
2 | #include <mbgl/text/check_max_angle.hpp> |
3 | #include <mbgl/util/constants.hpp> |
4 | #include <mbgl/util/interpolate.hpp> |
5 | |
6 | #include <cassert> |
7 | #include <cmath> |
8 | |
9 | namespace mbgl { |
10 | |
11 | float getAngleWindowSize(const float textLeft, const float textRight, const float glyphSize, const float boxScale) { |
12 | return (textLeft - textRight) != 0.0f ? |
13 | 3.0f / 5.0f * glyphSize * boxScale : |
14 | 0; |
15 | } |
16 | |
17 | float getLineLength(const GeometryCoordinates& line) { |
18 | float lineLength = 0; |
19 | for (auto it = line.begin(), end = line.end() - 1; it != end; it++) { |
20 | lineLength += util::dist<float>(a: *(it), b: *(it + 1)); |
21 | } |
22 | return lineLength; |
23 | } |
24 | |
25 | static Anchors resample(const GeometryCoordinates& line, |
26 | const float offset, |
27 | const float spacing, |
28 | const float angleWindowSize, |
29 | const float maxAngle, |
30 | const float labelLength, |
31 | const bool continuedLine, |
32 | const bool placeAtMiddle) { |
33 | const float halfLabelLength = labelLength / 2.0f; |
34 | const float lineLength = getLineLength(line); |
35 | |
36 | float distance = 0; |
37 | float markedDistance = offset - spacing; |
38 | |
39 | Anchors anchors; |
40 | |
41 | assert(spacing > 0.0); |
42 | |
43 | int i = 0; |
44 | for (auto it = line.begin(), end = line.end() - 1; it != end; it++, i++) { |
45 | const GeometryCoordinate& a = *(it); |
46 | const GeometryCoordinate& b = *(it + 1); |
47 | |
48 | const auto segmentDist = util::dist<float>(a, b); |
49 | const float angle = util::angle_to(a: b, b: a); |
50 | |
51 | while (markedDistance + spacing < distance + segmentDist) { |
52 | markedDistance += spacing; |
53 | |
54 | float t = (markedDistance - distance) / segmentDist, |
55 | x = util::interpolate(a: float(a.x), b: float(b.x), t), |
56 | y = util::interpolate(a: float(a.y), b: float(b.y), t); |
57 | |
58 | // Check that the point is within the tile boundaries and that |
59 | // the label would fit before the beginning and end of the line |
60 | // if placed at this point. |
61 | if (x >= 0 && x < util::EXTENT && y >= 0 && y < util::EXTENT && |
62 | markedDistance - halfLabelLength >= 0.0f && |
63 | markedDistance + halfLabelLength <= lineLength) { |
64 | Anchor anchor(::round(x: x), ::round(x: y), angle, 0.5f, i); |
65 | |
66 | if (!angleWindowSize || checkMaxAngle(line, anchor, labelLength, windowSize: angleWindowSize, maxAngle)) { |
67 | anchors.push_back(x: anchor); |
68 | } |
69 | } |
70 | } |
71 | |
72 | distance += segmentDist; |
73 | } |
74 | |
75 | if (!placeAtMiddle && anchors.empty() && !continuedLine) { |
76 | // The first attempt at finding anchors at which labels can be placed failed. |
77 | // Try again, but this time just try placing one anchor at the middle of the line. |
78 | // This has the most effect for short lines in overscaled tiles, since the |
79 | // initial offset used in overscaled tiles is calculated to align labels with positions in |
80 | // parent tiles instead of placing the label as close to the beginning as possible. |
81 | anchors = resample(line, offset: distance / 2, spacing, angleWindowSize, maxAngle, labelLength, continuedLine, placeAtMiddle: true); |
82 | } |
83 | |
84 | return anchors; |
85 | } |
86 | |
87 | Anchors getAnchors(const GeometryCoordinates& line, |
88 | float spacing, |
89 | const float maxAngle, |
90 | const float textLeft, |
91 | const float textRight, |
92 | const float iconLeft, |
93 | const float iconRight, |
94 | const float glyphSize, |
95 | const float boxScale, |
96 | const float overscaling) { |
97 | if (line.empty()) { |
98 | return {}; |
99 | } |
100 | |
101 | // Resample a line to get anchor points for labels and check that each |
102 | // potential label passes text-max-angle check and has enough froom to fit |
103 | // on the line. |
104 | |
105 | const float angleWindowSize = getAngleWindowSize(textLeft, textRight, glyphSize, boxScale); |
106 | |
107 | const float shapedLabelLength = fmax(x: textRight - textLeft, y: iconRight - iconLeft); |
108 | const float labelLength = shapedLabelLength * boxScale; |
109 | |
110 | // Is the line continued from outside the tile boundary? |
111 | const bool continuedLine = (line[0].x == 0 || line[0].x == util::EXTENT || line[0].y == 0 || line[0].y == util::EXTENT); |
112 | |
113 | // Is the label long, relative to the spacing? |
114 | // If so, adjust the spacing so there is always a minimum space of `spacing / 4` between label edges. |
115 | if (spacing - labelLength < spacing / 4) { |
116 | spacing = labelLength + spacing / 4; |
117 | } |
118 | |
119 | // Offset the first anchor by: |
120 | // Either half the label length plus a fixed extra offset if the line is not continued |
121 | // Or half the spacing if the line is continued. |
122 | |
123 | // For non-continued lines, add a bit of fixed extra offset to avoid collisions at T intersections. |
124 | const float = glyphSize * 2; |
125 | |
126 | const float offset = !continuedLine ? |
127 | std::fmod(x: (shapedLabelLength / 2 + fixedExtraOffset) * boxScale * overscaling, y: spacing) : |
128 | std::fmod(x: spacing / 2 * overscaling, y: spacing); |
129 | |
130 | return resample(line, offset, spacing, angleWindowSize, maxAngle, labelLength, continuedLine, placeAtMiddle: false); |
131 | } |
132 | |
133 | optional<Anchor> getCenterAnchor(const GeometryCoordinates& line, |
134 | const float maxAngle, |
135 | const float textLeft, |
136 | const float textRight, |
137 | const float iconLeft, |
138 | const float iconRight, |
139 | const float glyphSize, |
140 | const float boxScale) { |
141 | if (line.empty()) { |
142 | return {}; |
143 | } |
144 | |
145 | const float angleWindowSize = getAngleWindowSize(textLeft, textRight, glyphSize, boxScale); |
146 | const float labelLength = fmax(x: textRight - textLeft, y: iconRight - iconLeft) * boxScale; |
147 | |
148 | float prevDistance = 0; |
149 | const float centerDistance = getLineLength(line) / 2; |
150 | |
151 | int i = 0; |
152 | for (auto it = line.begin(), end = line.end() - 1; it != end; it++, i++) { |
153 | const GeometryCoordinate& a = *(it); |
154 | const GeometryCoordinate& b = *(it + 1); |
155 | |
156 | const auto segmentDistance = util::dist<float>(a, b); |
157 | |
158 | if (prevDistance + segmentDistance > centerDistance) { |
159 | // The center is on this segment |
160 | float t = (centerDistance - prevDistance) / segmentDistance, |
161 | x = util::interpolate(a: float(a.x), b: float(b.x), t), |
162 | y = util::interpolate(a: float(a.y), b: float(b.y), t); |
163 | |
164 | Anchor anchor(::round(x: x), ::round(x: y), util::angle_to(a: b, b: a), 0.5f, i); |
165 | |
166 | if (!angleWindowSize || checkMaxAngle(line, anchor, labelLength, windowSize: angleWindowSize, maxAngle)) { |
167 | return anchor; |
168 | } |
169 | } |
170 | |
171 | prevDistance += segmentDistance; |
172 | } |
173 | return {}; |
174 | } |
175 | |
176 | } // namespace mbgl |
177 | |