summaryrefslogtreecommitdiff
path: root/src/mbgl/text/get_anchors.cpp
blob: c38e181181b0bbc8aea3936dbb8d3ca53b0992e9 (plain)
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
169
170
171
172
173
174
175
176
177
178
#include <mbgl/text/get_anchors.hpp>
#include <mbgl/text/check_max_angle.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/util/interpolate.hpp>

#include <cassert>
#include <cmath>

namespace mbgl {
  
float getAngleWindowSize(const float textLeft, const float textRight, const float glyphSize, const float boxScale) {
    return (textLeft - textRight) != 0.0f ?
        3.0f / 5.0f * glyphSize * boxScale :
        0;
}

float getLineLength(const GeometryCoordinates& line) {
    float lineLength = 0;
    for (auto it = line.begin(), end = line.end() - 1; it != end; it++) {
        lineLength += util::dist<float>(*(it), *(it + 1));
    }
    return lineLength;
}

static Anchors 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) {
    const float halfLabelLength = labelLength / 2.0f;
    const float lineLength = getLineLength(line);

    float distance = 0;
    float markedDistance = offset - spacing;

    Anchors anchors;

    assert(spacing > 0.0);

    int i = 0;
    for (auto it = line.begin(), end = line.end() - 1; it != end; it++, i++) {
        const GeometryCoordinate& a = *(it);
        const GeometryCoordinate& b = *(it + 1);

        const auto segmentDist = util::dist<float>(a, b);
        const float angle = util::angle_to(b, a);

        while (markedDistance + spacing < distance + segmentDist) {
            markedDistance += spacing;

            float t = (markedDistance - distance) / segmentDist,
                  x = util::interpolate(float(a.x), float(b.x), t),
                  y = util::interpolate(float(a.y), float(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 < util::EXTENT && y >= 0 && y < util::EXTENT &&
                    markedDistance - halfLabelLength >= 0.0f &&
                    markedDistance + halfLabelLength <= lineLength) {
                Anchor anchor(::round(x), ::round(y), angle, 0.5f, i);

                if (!angleWindowSize || checkMaxAngle(line, anchor, labelLength, angleWindowSize, maxAngle)) {
                    anchors.push_back(anchor);
                }
            }
        }

        distance += segmentDist;
    }

    if (!placeAtMiddle && anchors.empty() && !continuedLine) {
        // 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, continuedLine, true);
    }

    return anchors;
}

Anchors 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) {
    if (line.empty()) {
        return {};
    }

    // 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 float angleWindowSize = getAngleWindowSize(textLeft, textRight, glyphSize, boxScale);

    const float shapedLabelLength = fmax(textRight - textLeft, iconRight - iconLeft);
    const float labelLength = shapedLabelLength * boxScale;

    // Is the line continued from outside the tile boundary?
    const bool continuedLine = (line[0].x == 0 || line[0].x == util::EXTENT || line[0].y == 0 || line[0].y == util::EXTENT);

    // 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 float fixedExtraOffset = glyphSize * 2;

    const float offset = !continuedLine ?
    std::fmod((shapedLabelLength / 2 + fixedExtraOffset) * boxScale * overscaling, spacing) :
    std::fmod(spacing / 2 * overscaling, spacing);

    return resample(line, offset, spacing, angleWindowSize, maxAngle, labelLength, continuedLine, false);
}

optional<Anchor> 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) {
    if (line.empty()) {
        return {};
    }
    
    const float angleWindowSize = getAngleWindowSize(textLeft, textRight, glyphSize, boxScale);
    const float labelLength = fmax(textRight - textLeft, iconRight - iconLeft) * boxScale;
    
    float prevDistance = 0;
    const float centerDistance = getLineLength(line) / 2;
    
    int i = 0;
    for (auto it = line.begin(), end = line.end() - 1; it != end; it++, i++) {
        const GeometryCoordinate& a = *(it);
        const GeometryCoordinate& b = *(it + 1);
        
        const auto segmentDistance = util::dist<float>(a, b);
        
        if (prevDistance + segmentDistance > centerDistance) {
            // The center is on this segment
            float t = (centerDistance - prevDistance) / segmentDistance,
                  x = util::interpolate(float(a.x), float(b.x), t),
                  y = util::interpolate(float(a.y), float(b.y), t);
            
            Anchor anchor(::round(x), ::round(y), util::angle_to(b, a), 0.5f, i);
            
            if (!angleWindowSize || checkMaxAngle(line, anchor, labelLength, angleWindowSize, maxAngle)) {
                return anchor;
            }

            return nullopt;
        }
        
        prevDistance += segmentDistance;
    }
    return {};
}

} // namespace mbgl