void SymbolBucket::addFeature(const std::vector<std::vector<Coordinate>> &lines, const Shaping &shapedText, const PositionedIcon &shapedIcon, const GlyphPositions &face) { const float minScale = 0.5f; const float glyphSize = 24.0f; const float fontScale = layout.text.size / glyphSize; const float textBoxScale = tilePixelRatio * fontScale; const float textMaxBoxScale = tilePixelRatio * layout.textMaxSize / glyphSize; const float iconBoxScale = tilePixelRatio * layout.icon.size; const float symbolSpacing = tilePixelRatio * layout.spacing; const bool avoidEdges = layout.avoidEdges && layout.placement != PlacementType::Line; const float textPadding = layout.text.padding * tilePixelRatio; const float iconPadding = layout.icon.padding * tilePixelRatio; const float textMaxAngle = layout.text.maxAngle * M_PI / 180; const bool textAlongLine = layout.text.rotationAlignment == RotationAlignmentType::Map && layout.placement == PlacementType::Line; const bool iconAlongLine = layout.icon.rotationAlignment == RotationAlignmentType::Map && layout.placement == PlacementType::Line; const bool mayOverlap = layout.text.allowOverlap || layout.icon.allowOverlap || layout.text.ignorePlacement || layout.icon.ignorePlacement; const bool isLine = layout.placement == PlacementType::Line; const float textRepeatDistance = symbolSpacing / 2; auto& clippedLines = isLine ? util::clipLines(lines, 0, 0, 4096, 4096) : lines; for (const auto& line : clippedLines) { if (line.empty()) continue; // Calculate the anchor points around which you want to place labels Anchors anchors = isLine ? getAnchors(line, symbolSpacing, textMaxAngle, shapedText.left, shapedText.right, shapedIcon.left, shapedIcon.right, glyphSize, textMaxBoxScale, overscaling) : Anchors({ Anchor(float(line[0].x), float(line[0].y), 0, minScale) }); // For each potential label, create the placement features used to check for collisions, and the quads use for rendering. for (Anchor &anchor : anchors) { if (shapedText && isLine) { if (anchorIsTooClose(shapedText.text, textRepeatDistance, anchor)) { continue; } } const bool inside = !(anchor.x < 0 || anchor.x > 4096 || anchor.y < 0 || anchor.y > 4096); if (avoidEdges && !inside) continue; // Normally symbol layers are drawn across tile boundaries. Only symbols // with their anchors within the tile boundaries are added to the buffers // to prevent symbols from being drawn twice. // // Symbols in layers with overlap are sorted in the y direction so that // symbols lower on the canvas are drawn on top of symbols near the top. // To preserve this order across tile boundaries these symbols can't // be drawn across tile boundaries. Instead they need to be included in // the buffers for both tiles and clipped to tile boundaries at draw time. // // TODO remove the `&& false` when is #1673 implemented const bool addToBuffers = (mode == MapMode::Still) || inside || (mayOverlap && false); symbolInstances.emplace_back(anchor, line, shapedText, shapedIcon, layout, addToBuffers, textBoxScale, textPadding, textAlongLine, iconBoxScale, iconPadding, iconAlongLine, face); } } }
void SymbolLayout::addFeature(const std::size_t index, const SymbolFeature& feature, const std::pair<Shaping, Shaping>& shapedTextOrientations, optional<PositionedIcon> shapedIcon, const GlyphPositionMap& glyphPositionMap, const OverscaledTileID& tileID, const std::string& sourceID) { const float minScale = 0.5f; const float glyphSize = 24.0f; const float layoutTextSize = layout.evaluate<TextSize>(zoom + 1, feature); const float layoutIconSize = layout.evaluate<IconSize>(zoom + 1, feature); const std::array<float, 2> textOffset = layout.evaluate<TextOffset>(zoom, feature); const std::array<float, 2> iconOffset = layout.evaluate<IconOffset>(zoom, feature); // To reduce the number of labels that jump around when zooming we need // to use a text-size value that is the same for all zoom levels. // This calculates text-size at a high zoom level so that all tiles can // use the same value when calculating anchor positions. const float textMaxSize = layout.evaluate<TextSize>(18, feature); const float fontScale = layoutTextSize / glyphSize; const float textBoxScale = tilePixelRatio * fontScale; const float textMaxBoxScale = tilePixelRatio * textMaxSize / glyphSize; const float iconBoxScale = tilePixelRatio * layoutIconSize; const float symbolSpacing = tilePixelRatio * layout.get<SymbolSpacing>(); const bool avoidEdges = layout.get<SymbolAvoidEdges>() && layout.get<SymbolPlacement>() != SymbolPlacementType::Line; const float textPadding = layout.get<TextPadding>() * tilePixelRatio; const float iconPadding = layout.get<IconPadding>() * tilePixelRatio; const float textMaxAngle = layout.get<TextMaxAngle>() * util::DEG2RAD; const SymbolPlacementType textPlacement = layout.get<TextRotationAlignment>() != AlignmentType::Map ? SymbolPlacementType::Point : layout.get<SymbolPlacement>(); const float textRepeatDistance = symbolSpacing / 2; IndexedSubfeature indexedFeature(feature.index, sourceLayer->getName(), bucketName, symbolInstances.size(), sourceID, tileID.canonical); auto addSymbolInstance = [&] (const GeometryCoordinates& line, Anchor& anchor) { // https://github.com/mapbox/vector-tile-spec/tree/master/2.1#41-layers // +-------------------+ Symbols with anchors located on tile edges // |(0,0) || are duplicated on neighbor tiles. // | || // | || In continuous mode, to avoid overdraw we // | || skip symbols located on the extent edges. // | Tile || In still mode, we include the features in // | || the buffers for both tiles and clip them // | || at draw time. // | || // +-------------------| In this scenario, the inner bounding box // +-------------------+ is called 'withinPlus0', and the outer // (extent,extent) is called 'inside'. const bool withinPlus0 = anchor.point.x >= 0 && anchor.point.x < util::EXTENT && anchor.point.y >= 0 && anchor.point.y < util::EXTENT; const bool inside = withinPlus0 || anchor.point.x == util::EXTENT || anchor.point.y == util::EXTENT; if (avoidEdges && !inside) return; if (mode == MapMode::Tile || withinPlus0) { symbolInstances.emplace_back(anchor, line, shapedTextOrientations, shapedIcon, layout.evaluate(zoom, feature), layoutTextSize, symbolInstances.size(), textBoxScale, textPadding, textPlacement, textOffset, iconBoxScale, iconPadding, iconOffset, glyphPositionMap, indexedFeature, index, feature.text.value_or(std::u16string()), overscaling); } }; const auto& type = feature.getType(); if (layout.get<SymbolPlacement>() == SymbolPlacementType::Line) { auto clippedLines = util::clipLines(feature.geometry, 0, 0, util::EXTENT, util::EXTENT); for (const auto& line : clippedLines) { Anchors anchors = getAnchors(line, symbolSpacing, textMaxAngle, (shapedTextOrientations.second ?: shapedTextOrientations.first).left, (shapedTextOrientations.second ?: shapedTextOrientations.first).right, (shapedIcon ? shapedIcon->left() : 0), (shapedIcon ? shapedIcon->right() : 0), glyphSize, textMaxBoxScale, overscaling); for (auto& anchor : anchors) { if (!feature.text || !anchorIsTooClose(*feature.text, textRepeatDistance, anchor)) { addSymbolInstance(line, anchor); } } } } else if (type == FeatureType::Polygon) {