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