1 #include <mbgl/text/cross_tile_symbol_index.hpp>
2 #include <mbgl/layout/symbol_instance.hpp>
3 #include <mbgl/renderer/buckets/symbol_bucket.hpp>
4 #include <mbgl/renderer/render_tile.hpp>
5 #include <mbgl/tile/tile.hpp>
6
7 namespace mbgl {
8
9
TileLayerIndex(OverscaledTileID coord_,std::vector<SymbolInstance> & symbolInstances,uint32_t bucketInstanceId_)10 TileLayerIndex::TileLayerIndex(OverscaledTileID coord_, std::vector<SymbolInstance>& symbolInstances, uint32_t bucketInstanceId_)
11 : coord(coord_), bucketInstanceId(bucketInstanceId_) {
12 for (SymbolInstance& symbolInstance : symbolInstances) {
13 indexedSymbolInstances[symbolInstance.key].emplace_back(symbolInstance.crossTileID, getScaledCoordinates(symbolInstance, coord));
14 }
15 }
16
getScaledCoordinates(SymbolInstance & symbolInstance,const OverscaledTileID & childTileCoord)17 Point<int64_t> TileLayerIndex::getScaledCoordinates(SymbolInstance& symbolInstance, const OverscaledTileID& childTileCoord) {
18 // Round anchor positions to roughly 4 pixel grid
19 const double roundingFactor = 512.0 / util::EXTENT / 2.0;
20 const double scale = roundingFactor / std::pow(2, childTileCoord.canonical.z - coord.canonical.z);
21 return {
22 static_cast<int64_t>(std::floor((childTileCoord.canonical.x * util::EXTENT + symbolInstance.anchor.point.x) * scale)),
23 static_cast<int64_t>(std::floor((childTileCoord.canonical.y * util::EXTENT + symbolInstance.anchor.point.y) * scale))
24 };
25 }
26
findMatches(std::vector<SymbolInstance> & symbolInstances,const OverscaledTileID & newCoord,std::set<uint32_t> & zoomCrossTileIDs)27 void TileLayerIndex::findMatches(std::vector<SymbolInstance>& symbolInstances, const OverscaledTileID& newCoord, std::set<uint32_t>& zoomCrossTileIDs) {
28 float tolerance = coord.canonical.z < newCoord.canonical.z ? 1 : std::pow(2, coord.canonical.z - newCoord.canonical.z);
29
30 for (auto& symbolInstance : symbolInstances) {
31 if (symbolInstance.crossTileID) {
32 // already has a match, skip
33 continue;
34 }
35
36 auto it = indexedSymbolInstances.find(symbolInstance.key);
37 if (it == indexedSymbolInstances.end()) {
38 // No symbol with this key in this bucket
39 continue;
40 }
41
42 auto scaledSymbolCoord = getScaledCoordinates(symbolInstance, newCoord);
43
44 for (IndexedSymbolInstance& thisTileSymbol: it->second) {
45 // Return any symbol with the same keys whose coordinates are within 1
46 // grid unit. (with a 4px grid, this covers a 12px by 12px area)
47 if (std::abs(thisTileSymbol.coord.x - scaledSymbolCoord.x) <= tolerance &&
48 std::abs(thisTileSymbol.coord.y - scaledSymbolCoord.y) <= tolerance &&
49 zoomCrossTileIDs.find(thisTileSymbol.crossTileID) == zoomCrossTileIDs.end()) {
50 // Once we've marked ourselves duplicate against this parent symbol,
51 // don't let any other symbols at the same zoom level duplicate against
52 // the same parent (see issue #10844)
53 zoomCrossTileIDs.insert(thisTileSymbol.crossTileID);
54 symbolInstance.crossTileID = thisTileSymbol.crossTileID;
55 break;
56 }
57 }
58 }
59 }
60
CrossTileSymbolLayerIndex()61 CrossTileSymbolLayerIndex::CrossTileSymbolLayerIndex() {
62 }
63
64 /*
65 * Sometimes when a user pans across the antimeridian the longitude value gets wrapped.
66 * To prevent labels from flashing out and in we adjust the tileID values in the indexes
67 * so that they match the new wrapped version of the map.
68 */
handleWrapJump(float newLng)69 void CrossTileSymbolLayerIndex::handleWrapJump(float newLng) {
70
71 const int wrapDelta = ::round((newLng - lng) / 360);
72 if (wrapDelta != 0) {
73 std::map<uint8_t, std::map<OverscaledTileID,TileLayerIndex>> newIndexes;
74 for (auto& zoomIndex : indexes) {
75 std::map<OverscaledTileID,TileLayerIndex> newZoomIndex;
76 for (auto& index : zoomIndex.second) {
77 // change the tileID's wrap and move its index
78 index.second.coord = index.second.coord.unwrapTo(index.second.coord.wrap + wrapDelta);
79 newZoomIndex.emplace(index.second.coord, std::move(index.second));
80 }
81 newIndexes.emplace(zoomIndex.first, std::move(newZoomIndex));
82 }
83
84 indexes = std::move(newIndexes);
85 }
86
87 lng = newLng;
88 }
89
addBucket(const OverscaledTileID & tileID,SymbolBucket & bucket,uint32_t & maxCrossTileID)90 bool CrossTileSymbolLayerIndex::addBucket(const OverscaledTileID& tileID, SymbolBucket& bucket, uint32_t& maxCrossTileID) {
91 const auto& thisZoomIndexes = indexes[tileID.overscaledZ];
92 auto previousIndex = thisZoomIndexes.find(tileID);
93 if (previousIndex != thisZoomIndexes.end()) {
94 if (previousIndex->second.bucketInstanceId == bucket.bucketInstanceId) {
95 return false;
96 } else {
97 // We're replacing this bucket with an updated version
98 // Remove the old bucket's "used crossTileIDs" now so that the new bucket can claim them.
99 // We have to keep the old index entries themselves until the end of 'addBucket' so
100 // that we can copy them with 'findMatches'.
101 removeBucketCrossTileIDs(tileID.overscaledZ, previousIndex->second);
102 }
103 }
104
105 for (auto& symbolInstance: bucket.symbolInstances) {
106 symbolInstance.crossTileID = 0;
107 }
108
109 for (auto& it : indexes) {
110 auto zoom = it.first;
111 auto zoomIndexes = it.second;
112 if (zoom > tileID.overscaledZ) {
113 for (auto& childIndex : zoomIndexes) {
114 if (childIndex.second.coord.isChildOf(tileID)) {
115 childIndex.second.findMatches(bucket.symbolInstances, tileID, usedCrossTileIDs[tileID.overscaledZ]);
116 }
117 }
118 } else {
119 auto parentTileID = tileID.scaledTo(zoom);
120 auto parentIndex = zoomIndexes.find(parentTileID);
121 if (parentIndex != zoomIndexes.end()) {
122 parentIndex->second.findMatches(bucket.symbolInstances, tileID, usedCrossTileIDs[tileID.overscaledZ]);
123 }
124 }
125 }
126
127 for (auto& symbolInstance : bucket.symbolInstances) {
128 if (!symbolInstance.crossTileID) {
129 // symbol did not match any known symbol, assign a new id
130 symbolInstance.crossTileID = ++maxCrossTileID;
131 usedCrossTileIDs[tileID.overscaledZ].insert(symbolInstance.crossTileID);
132 }
133 }
134
135
136 indexes[tileID.overscaledZ].erase(tileID);
137 indexes[tileID.overscaledZ].emplace(tileID, TileLayerIndex(tileID, bucket.symbolInstances, bucket.bucketInstanceId));
138 return true;
139 }
140
removeBucketCrossTileIDs(uint8_t zoom,const TileLayerIndex & removedBucket)141 void CrossTileSymbolLayerIndex::removeBucketCrossTileIDs(uint8_t zoom, const TileLayerIndex& removedBucket) {
142 for (auto key : removedBucket.indexedSymbolInstances) {
143 for (auto indexedSymbolInstance : key.second) {
144 usedCrossTileIDs[zoom].erase(indexedSymbolInstance.crossTileID);
145 }
146 }
147 }
148
removeStaleBuckets(const std::unordered_set<uint32_t> & currentIDs)149 bool CrossTileSymbolLayerIndex::removeStaleBuckets(const std::unordered_set<uint32_t>& currentIDs) {
150 bool tilesChanged = false;
151 for (auto& zoomIndexes : indexes) {
152 for (auto it = zoomIndexes.second.begin(); it != zoomIndexes.second.end();) {
153 if (!currentIDs.count(it->second.bucketInstanceId)) {
154 removeBucketCrossTileIDs(zoomIndexes.first, it->second);
155 it = zoomIndexes.second.erase(it);
156 tilesChanged = true;
157 } else {
158 ++it;
159 }
160 }
161 }
162 return tilesChanged;
163 }
164
CrossTileSymbolIndex()165 CrossTileSymbolIndex::CrossTileSymbolIndex() {}
166
addLayer(RenderSymbolLayer & symbolLayer,float lng)167 bool CrossTileSymbolIndex::addLayer(RenderSymbolLayer& symbolLayer, float lng) {
168
169 auto& layerIndex = layerIndexes[symbolLayer.getID()];
170
171 bool symbolBucketsChanged = false;
172 std::unordered_set<uint32_t> currentBucketIDs;
173
174 layerIndex.handleWrapJump(lng);
175
176 for (RenderTile& renderTile : symbolLayer.renderTiles) {
177 if (!renderTile.tile.isRenderable()) {
178 continue;
179 }
180
181 auto bucket = renderTile.tile.getBucket<SymbolBucket>(*symbolLayer.baseImpl);
182 if (!bucket) {
183 continue;
184 }
185 SymbolBucket& symbolBucket = *bucket;
186
187 if (symbolBucket.bucketLeaderID != symbolLayer.getID()) {
188 // Only add this layer if it's the "group leader" for the bucket
189 continue;
190 }
191
192 if (!symbolBucket.bucketInstanceId) {
193 symbolBucket.bucketInstanceId = ++maxBucketInstanceId;
194 }
195
196 const bool bucketAdded = layerIndex.addBucket(renderTile.tile.id, symbolBucket, maxCrossTileID);
197 symbolBucketsChanged = symbolBucketsChanged || bucketAdded;
198 currentBucketIDs.insert(symbolBucket.bucketInstanceId);
199 }
200
201 if (layerIndex.removeStaleBuckets(currentBucketIDs)) {
202 symbolBucketsChanged = true;
203 }
204 return symbolBucketsChanged;
205 }
206
pruneUnusedLayers(const std::set<std::string> & usedLayers)207 void CrossTileSymbolIndex::pruneUnusedLayers(const std::set<std::string>& usedLayers) {
208 std::vector<std::string> unusedLayers;
209 for (auto layerIndex : layerIndexes) {
210 if (usedLayers.find(layerIndex.first) == usedLayers.end()) {
211 unusedLayers.push_back(layerIndex.first);
212 }
213 }
214 for (auto unusedLayer : unusedLayers) {
215 layerIndexes.erase(unusedLayer);
216 }
217 }
218
reset()219 void CrossTileSymbolIndex::reset() {
220 layerIndexes.clear();
221 }
222
223 } // namespace mbgl
224
225