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 
getAngleWindowSize(const float textLeft,const float textRight,const float glyphSize,const float boxScale)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 
getLineLength(const GeometryCoordinates & line)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>(*(it), *(it + 1));
21     }
22     return lineLength;
23 }
24 
resample(const GeometryCoordinates & line,const float offset,const float spacing,const float angleWindowSize,const float maxAngle,const float labelLength,const bool continuedLine,const bool placeAtMiddle)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(b, a);
50 
51         while (markedDistance + spacing < distance + segmentDist) {
52             markedDistance += spacing;
53 
54             float t = (markedDistance - distance) / segmentDist,
55                   x = util::interpolate(float(a.x), float(b.x), t),
56                   y = util::interpolate(float(a.y), 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), ::round(y), angle, 0.5f, i);
65 
66                 if (!angleWindowSize || checkMaxAngle(line, anchor, labelLength, angleWindowSize, maxAngle)) {
67                     anchors.push_back(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, distance / 2, spacing, angleWindowSize, maxAngle, labelLength, continuedLine, true);
82     }
83 
84     return anchors;
85 }
86 
getAnchors(const GeometryCoordinates & line,float spacing,const float maxAngle,const float textLeft,const float textRight,const float iconLeft,const float iconRight,const float glyphSize,const float boxScale,const float overscaling)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(textRight - textLeft, 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 fixedExtraOffset = glyphSize * 2;
125 
126     const float offset = !continuedLine ?
127     std::fmod((shapedLabelLength / 2 + fixedExtraOffset) * boxScale * overscaling, spacing) :
128     std::fmod(spacing / 2 * overscaling, spacing);
129 
130     return resample(line, offset, spacing, angleWindowSize, maxAngle, labelLength, continuedLine, false);
131 }
132 
getCenterAnchor(const GeometryCoordinates & line,const float maxAngle,const float textLeft,const float textRight,const float iconLeft,const float iconRight,const float glyphSize,const float boxScale)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(textRight - textLeft, 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(float(a.x), float(b.x), t),
162                   y = util::interpolate(float(a.y), float(b.y), t);
163 
164             Anchor anchor(::round(x), ::round(y), util::angle_to(b, a), 0.5f, i);
165 
166             if (!angleWindowSize || checkMaxAngle(line, anchor, labelLength, angleWindowSize, maxAngle)) {
167                 return anchor;
168             }
169         }
170 
171         prevDistance += segmentDistance;
172     }
173     return {};
174 }
175 
176 } // namespace mbgl
177