Well-placed labels can be the difference between a sloppy map and a beautiful one. Labels need to clearly identify features without obscuring the map.
好的標注位置可以作為潦草的地圖和漂亮的地圖之間的區別。標簽需要清晰地識別要素而不模糊地圖。
The normal requirements for map labelling are to place labels as clearly as possible without any overlap. Regular maps just need to avoid label overlap for a single, fixed zoom level and rotation.
地圖標注的正常要求是盡可能清晰地放置標簽,而不需要重疊。正常地圖只需要對一個單一的固定縮放級別和旋轉來避免標簽重疊。
Good label placement is a hard problem that we solve for Mapbox GL. We need our label placements to work at any zoom and any rotation. We need labels placements to be continuous, so that labels don’t jump around when zooming or rotating. We need labels to be seamless across tiles. We need to support changing font sizes as you zoom. And it all needs to be fast.
好的標注安置是我們解決的一個難題。我們需要我們的標簽放置在任何縮放和任何旋轉中都能正常展示。我們需要標注安置是漸進型的,這樣標簽不至於在旋轉、縮放時跳來跳去。我們需要標注在瓦片上上無縫地連接。當你縮放時,我們需要支持更改字體大小。而且一切都需要快速。
Placement needs to support both horizontal labels, as well as curved labels which follow a line. Both types of labels need to behave smoothly when zooming and rotating. Labels can never overlap, even when rotating. Horizontal labels stay horizontal and curved labels rotate with the map. Labels are flipped to avoid being drawn upside down and curved labels smoothly slide along roads.
安置需要同時支持水平文字標注和沿着一條曲線彎曲的標注。兩種類型的標簽都需要在縮放和旋轉時表現得很平滑。標簽永遠不能重疊,即使旋轉時也不能重疊。水平標簽保持水平且曲線標簽隨着地圖旋轉。文本標注會被翻轉以避免被渲染成字頭向下(文字倒置)的情況,彎曲的標簽沿着道路平滑滑動。
There is plenty of academic research on label placement, but most of this applies to maps with fixed rotation and with separate zoom levels. Dynamic maps with continuous zooming, panning, and rotation need a completely different approach. Our implementation expands on a paper by Been and Yap that establishes four ideal requirements for continuous and interactive labelling:
標簽安置方面的學術研究很多,但大部分都適用於固定旋轉和分立的縮放級別的地圖。具有連續縮放、平移和旋轉的動態地圖需要完全不同的安置方法。我們的實現擴展了由Been 和 Yap編寫的論文,該論文為連續和交互式標簽確立了四個理想要求:
- Labels should not disappear when zooming in or appear when zooming out.
- Labels should not disappear or appear when panning except when sliding out of view.
- Labels should not jump around, but instead should be anchored.
- Label placement should be deterministic, no matter how you got to the current view.
- 標簽不應該在放大時消失,在縮小時出現。
- 標簽不應該在平移時消失或出現,除非滑出視圖。
- 標簽不應該跳來跳去,而是應該被錨定。
- 標簽的位置應該是確定的,無論你怎么到當前視圖。
The paper provides guidance on implementing this for horizontal labels, but we go further by supporting rotation and curved labels.
該論文為水平標注的實現提供了指導,但是我們通過支持旋轉和彎曲標簽來更進一步完善文本安置。
- Our implementation has two steps:
- Preprocessing
- Rendering
- 我們的實現有兩個步驟:
- 預處理
- 渲染
The rendering step needs to be fast so that Mapbox GL can rerender the entire map every frame for smooth interaction. Most of the placement work happens in the preprocessing step:
- Generate anchor points for each label.
- Calculate the positions of individual glyphs relative to the anchors.
- Calculate the zoom levels at which the labels and glyphs can be shown without overlap.
- Calculate the rotation range in which the label can be shown.
渲染步驟需要足夠快以便為了mapbox gl 能夠在每一幀中都能重新渲染整個地圖以達到順滑的交互效果。大多數的安置個工作發生在預處理的步驟中:
- 為每個標簽生成錨點。
- 計算各個字形相對於錨點的位置。
- 計算該標簽和符號可以沒有重疊顯示的縮放級別。
- 計算可以顯示標簽的旋轉范圍。
Generating anchor points
Each label has an anchor. An anchor is the point at which a label is positioned when zooming or rotating.
每一個標簽都有一個錨點,錨點是指縮放活旋轉時被定位位置的點。
Labels for point features have a single anchor, the point.
對於點要素的標簽只有一個錨點,就是這個點本身。
For lines, we want to show multiple labels so we interpolate along the line adding an anchor every x pixels. Distance between labels changes when zooming, so we add a minimum zoom level for each anchor to maintain appropriate spacing. Fewer labels are shown at lower zoom levels and more appear as you zoom in.
對於線,我們想顯示多個標簽,所以我們沿着這條線對個x像素的都插入一個錨點。縮放時標簽之間的距離發生變化,因此我們為每個錨添加最小縮放級別以保持適當的間距。較少的標簽顯示在較低縮放級別,並且隨着您縮放的時候出現更多。
Generating positioned glyphs for each anchor
For each piece of text we already have a list of glyphs and their positions, but these positions need to be adjusted for curved labels.
對於每一段文本,我們已經有了一個字形和它們位置的列表,但是這些位置對於彎曲標簽來說需要調整。
During the render step we can only shift glyphs along a straight line. To draw curved text we need to add multiple copies of glyphs — one for each line segment a glyph appears on. Each of these glyphs have minimum and maximum zoom levels that hide the glyph when it slides off the end of a segment so that only one instance of each original glyph is shown at the same time.
在渲染步驟中,我們只能沿着直線移動字形。要繪制曲面文本,我們需要添加多個字形副本每——一個線段上只出現一個字形。每個字形都有最小和最大縮放級別,當它滑動到段末端時隱藏字形符號,以便每一個原始字形的實例在同一時間只有一個能顯示。
Usually these glyphs are completely hidden when out of range, but here they are shown with a reduced opacity:
通常這些符號在超出范圍時完全隱藏起來,但是在這里它們顯示出了一個減少的透明度:
Restricting the zoom range
To avoid label collisions, we need to restrict the zoom level at which a label is first shown. As you zoom in, labels get spaced further apart, opening room for new labels. Once a label is shown, it will not be hidden as you zoom in.
為了避免文本標注碰撞,我們需要在文本標注第一次顯示時限制它的縮放級別。當你放大時,標簽間距會進一步增大,為新的標簽出現打開空間。一旦標簽顯示出來,它在放大時就不會被隱藏。
We use an R-tree that contains already-placed labels to narrow down which labels might collide. We then calculate the zoom level at which the two labels will fit side-by-side. It is safe to show the label for any zoom level higher than this one.
我們使用一個r樹,它包含已經放置的標簽來減少可能會碰撞的標簽。然后計算兩個標簽將並排安裝的縮放級別。對於任何縮放級別高於此的級別下顯示這個標簽是安全的。
Horizontal-horizontal collision
There are eight possible angles at which a pair of horizontal labels could collide. Each of these possible collisions is checked with some trigonometry.
有八個可能的角度會使得一對水平標簽發生碰撞。這些可能碰撞的每一個都用一些三角學檢驗來檢驗。
彎曲-水平旋轉碰撞
A curved-horizontal collision occurs when a corner of one label’s bounding box intersects an edge of the other label’s bounding box. For each of the eight bounding box corners, we calculate the angles at which a circle (formed by that point being rotated around the label’s anchor) intersects the edges of the other box. These are the angles at which a collision would begin and end.
當一個標簽的邊框的角與另一個標簽的邊框的邊緣相交時,出現了彎曲水平碰撞。對於八個包圍盒角的每個角落,我們計算了一個圓(由該點旋轉圍繞標簽的錨)相交於另一個盒子的邊緣的一些角度。這些就是碰撞開始和結束的角度。
Seamlessness
Mapbox GL downloads vector tiles with data for the area and zoom level it is currently displaying. When new tiles are downloaded and their labels have been placed, an old tile’s label may need to be hidden to make way for a more important label. This will be handled in a resolution step that has not yet been implemented.
Mapbox GL下載當前顯示范圍和縮放級別的矢量瓦片。當新的瓦片下載完成,且它們的標簽位置已經計算完成,一個舊的瓦片的標注可能需要隱藏讓位給一個更重要的標簽。這將在尚未實現的步驟由解析處理。