[core] Fix symbol rendering for multipoints

Ports https://github.com/mapbox/mapbox-gl-js/pull/3763 and https://github.com/mapbox/mapbox-gl-js/pull/3806.
This commit is contained in:
John Firebaugh
2016-12-14 14:58:06 -08:00
parent 3dcff6c3e1
commit dad57df38e
6 changed files with 99 additions and 84 deletions

View File

@@ -24,7 +24,7 @@
"lodash": "^4.16.4",
"mapbox-gl": "mapbox/mapbox-gl-js#ab836206d415ca3a74257a3066d11a54ab2838cb",
"mapbox-gl-style-spec": "mapbox/mapbox-gl-style-spec#49e8b407bdbbe6f7c92dbcb56d3d51f425fc2653",
"mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#bbe948a30c3879b77a220eaa1336f61d46db5953",
"mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#e1ada02a706fd124fc3441fd3a2b3cda67960ff5",
"mkdirp": "^0.5.1",
"node-cmake": "^1.2.1",
"pixelmatch": "^4.0.2",

View File

@@ -9,6 +9,7 @@ namespace mbgl {
class SymbolFeature {
public:
FeatureType type;
GeometryCollection geometry;
optional<std::u16string> text;
optional<std::string> icon;

View File

@@ -103,16 +103,8 @@ SymbolLayout::SymbolLayout(std::string bucketName_,
}
if (ft.text || ft.icon) {
auto &multiline = ft.geometry;
GeometryCollection geometryCollection = feature->getGeometries();
for (auto& line : geometryCollection) {
multiline.emplace_back();
for (auto& point : line) {
multiline.back().emplace_back(point.x, point.y);
}
}
ft.type = feature->getType();
ft.geometry = feature->getGeometries();
features.push_back(std::move(ft));
}
}
@@ -229,17 +221,17 @@ void SymbolLayout::prepare(uintptr_t tileUID,
// if either shapedText or icon position is present, add the feature
if (shapedText || shapedIcon) {
addFeature(feature.geometry, shapedText, shapedIcon, face, feature.index);
addFeature(feature, shapedText, shapedIcon, face);
}
}
features.clear();
}
void SymbolLayout::addFeature(const GeometryCollection &lines,
const Shaping &shapedText, const PositionedIcon &shapedIcon, const GlyphPositions &face, const size_t index) {
void SymbolLayout::addFeature(const SymbolFeature& feature,
const Shaping& shapedText,
const PositionedIcon& shapedIcon,
const GlyphPositions& face) {
const float minScale = 0.5f;
const float glyphSize = 24.0f;
@@ -258,55 +250,75 @@ void SymbolLayout::addFeature(const GeometryCollection &lines,
const SymbolPlacementType iconPlacement = layout.get<IconRotationAlignment>() != AlignmentType::Map
? SymbolPlacementType::Point
: layout.get<SymbolPlacement>();
const bool isLine = layout.get<SymbolPlacement>() == SymbolPlacementType::Line;
const float textRepeatDistance = symbolSpacing / 2;
IndexedSubfeature indexedFeature = {feature.index, sourceLayerName, bucketName, symbolInstances.size()};
auto& clippedLines = isLine ?
util::clipLines(lines, 0, 0, util::EXTENT, util::EXTENT) :
lines;
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;
IndexedSubfeature indexedFeature = {index, sourceLayerName, bucketName, symbolInstances.size()};
if (avoidEdges && !inside) return;
for (const auto& line : clippedLines) {
if (line.empty()) continue;
const bool addToBuffers = mode == MapMode::Still || withinPlus0;
// 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) });
symbolInstances.emplace_back(anchor, line, shapedText, shapedIcon, layout, addToBuffers, symbolInstances.size(),
textBoxScale, textPadding, textPlacement,
iconBoxScale, iconPadding, iconPlacement,
face, indexedFeature);
};
// 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;
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,
shapedText.left,
shapedText.right,
shapedIcon.left,
shapedIcon.right,
glyphSize,
textMaxBoxScale,
overscaling);
for (auto& anchor : anchors) {
if (!shapedText || !anchorIsTooClose(shapedText.text, textRepeatDistance, anchor)) {
addSymbolInstance(line, 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) continue;
const bool addToBuffers = mode == MapMode::Still || withinPlus0;
symbolInstances.emplace_back(anchor, line, shapedText, shapedIcon, layout, addToBuffers, symbolInstances.size(),
textBoxScale, textPadding, textPlacement,
iconBoxScale, iconPadding, iconPlacement,
face, indexedFeature);
}
} else if (feature.type == FeatureType::Polygon) {
// TODO: pole of inaccessibility
for (const auto& ring : feature.geometry) {
for (const auto& point : ring) {
Anchor anchor(point.x, point.y, 0, minScale);
addSymbolInstance(ring, anchor);
}
}
} else if (feature.type == FeatureType::LineString) {
for (const auto& line : feature.geometry) {
Anchor anchor(line[0].x, line[0].y, 0, minScale);
addSymbolInstance(line, anchor);
}
} else if (feature.type == FeatureType::Point) {
for (const auto& points : feature.geometry) {
for (const auto& point : points) {
Anchor anchor(point.x, point.y, 0, minScale);
addSymbolInstance({point}, anchor);
}
}
}
}

View File

@@ -59,11 +59,10 @@ public:
const std::string sourceLayerName;
private:
void addFeature(const GeometryCollection&,
void addFeature(const SymbolFeature&,
const Shaping& shapedText,
const PositionedIcon& shapedIcon,
const GlyphPositions& face,
const size_t index);
const GlyphPositions& face);
bool anchorIsTooClose(const std::u16string& text, const float repeatDistance, const Anchor&);
std::map<std::u16string, std::vector<Anchor>> compareText;

View File

@@ -72,6 +72,7 @@ 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

View File

@@ -6,24 +6,26 @@
const std::u16string aaa = u"a";
const std::u16string bbb = u"b";
using namespace mbgl;
TEST(MergeLines, SameText) {
// merges lines with the same text
std::vector<mbgl::SymbolFeature> input1 = {
{ {{{0, 0}, {1, 0}, {2, 0}}}, aaa, {}, 0 },
{ {{{4, 0}, {5, 0}, {6, 0}}}, bbb, {}, 0 },
{ {{{8, 0}, {9, 0}}}, aaa, {}, 0 },
{ {{{2, 0}, {3, 0}, {4, 0}}}, aaa, {}, 0 },
{ {{{6, 0}, {7, 0}, {8, 0}}}, aaa, {}, 0 },
{ {{{5, 0}, {6, 0}}}, aaa, {}, 0 }
{ FeatureType::LineString, {{{0, 0}, {1, 0}, {2, 0}}}, aaa, {}, 0 },
{ FeatureType::LineString, {{{4, 0}, {5, 0}, {6, 0}}}, bbb, {}, 0 },
{ FeatureType::LineString, {{{8, 0}, {9, 0}}}, aaa, {}, 0 },
{ FeatureType::LineString, {{{2, 0}, {3, 0}, {4, 0}}}, aaa, {}, 0 },
{ FeatureType::LineString, {{{6, 0}, {7, 0}, {8, 0}}}, aaa, {}, 0 },
{ FeatureType::LineString, {{{5, 0}, {6, 0}}}, aaa, {}, 0 }
};
const std::vector<mbgl::SymbolFeature> expected1 = {
{ {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}}}, aaa, {}, 0 },
{ {{{4, 0}, {5, 0}, {6, 0}}}, bbb, {}, 0 },
{ {{{5, 0}, {6, 0}, {7, 0}, {8, 0}, {9, 0}}}, aaa, {}, 0 },
{ {{}}, aaa, {}, 0 },
{ {{}}, aaa, {}, 0 },
{ {{}}, aaa, {}, 0 }
{ FeatureType::LineString, {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}}}, aaa, {}, 0 },
{ FeatureType::LineString, {{{4, 0}, {5, 0}, {6, 0}}}, bbb, {}, 0 },
{ FeatureType::LineString, {{{5, 0}, {6, 0}, {7, 0}, {8, 0}, {9, 0}}}, aaa, {}, 0 },
{ FeatureType::LineString, {{}}, aaa, {}, 0 },
{ FeatureType::LineString, {{}}, aaa, {}, 0 },
{ FeatureType::LineString, {{}}, aaa, {}, 0 }
};
mbgl::util::mergeLines(input1);
@@ -36,15 +38,15 @@ TEST(MergeLines, SameText) {
TEST(MergeLines, BothEnds) {
// mergeLines handles merge from both ends
std::vector<mbgl::SymbolFeature> input2 = {
{ {{{0, 0}, {1, 0}, {2, 0}}}, aaa, {}, 0 },
{ {{{4, 0}, {5, 0}, {6, 0}}}, aaa, {}, 0 },
{ {{{2, 0}, {3, 0}, {4, 0}}}, aaa, {}, 0 }
{ FeatureType::LineString, {{{0, 0}, {1, 0}, {2, 0}}}, aaa, {}, 0 },
{ FeatureType::LineString, {{{4, 0}, {5, 0}, {6, 0}}}, aaa, {}, 0 },
{ FeatureType::LineString, {{{2, 0}, {3, 0}, {4, 0}}}, aaa, {}, 0 }
};
const std::vector<mbgl::SymbolFeature> expected2 = {
{ {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}}}, aaa, {}, 0 },
{ {{}}, aaa, {}, 0 },
{ {{}}, aaa, {}, 0 }
{ FeatureType::LineString, {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}}}, aaa, {}, 0 },
{ FeatureType::LineString, {{}}, aaa, {}, 0 },
{ FeatureType::LineString, {{}}, aaa, {}, 0 }
};
mbgl::util::mergeLines(input2);
@@ -57,15 +59,15 @@ TEST(MergeLines, BothEnds) {
TEST(MergeLines, CircularLines) {
// mergeLines handles circular lines
std::vector<mbgl::SymbolFeature> input3 = {
{ {{{0, 0}, {1, 0}, {2, 0}}}, aaa, {}, 0 },
{ {{{2, 0}, {3, 0}, {4, 0}}}, aaa, {}, 0 },
{ {{{4, 0}, {0, 0}}}, aaa, {}, 0 }
{ FeatureType::LineString, {{{0, 0}, {1, 0}, {2, 0}}}, aaa, {}, 0 },
{ FeatureType::LineString, {{{2, 0}, {3, 0}, {4, 0}}}, aaa, {}, 0 },
{ FeatureType::LineString, {{{4, 0}, {0, 0}}}, aaa, {}, 0 }
};
const std::vector<mbgl::SymbolFeature> expected3 = {
{ {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {0, 0}}}, aaa, {}, 0 },
{ {{}}, aaa, {}, 0 },
{ {{}}, aaa, {}, 0 }
{ FeatureType::LineString, {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {0, 0}}}, aaa, {}, 0 },
{ FeatureType::LineString, {{}}, aaa, {}, 0 },
{ FeatureType::LineString, {{}}, aaa, {}, 0 }
};
mbgl::util::mergeLines(input3);