From cc8d32c704c5e85e6102c11bf537a6c9f877d18c Mon Sep 17 00:00:00 2001 From: Bruno de Oliveira Abinader Date: Thu, 20 Oct 2016 19:11:34 +0300 Subject: [PATCH 1/7] [core] Cleanup function signatures in CollisionTile - Prefer pass-by-value for primitive types. - Use floating point precision for yStretch to match receiving type. --- src/mbgl/text/collision_tile.cpp | 8 ++++---- src/mbgl/text/collision_tile.hpp | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/mbgl/text/collision_tile.cpp b/src/mbgl/text/collision_tile.cpp index d6fc1a6adae..3681581455c 100644 --- a/src/mbgl/text/collision_tile.cpp +++ b/src/mbgl/text/collision_tile.cpp @@ -36,7 +36,7 @@ CollisionTile::CollisionTile(PlacementConfig config_) : config(std::move(config_ // The amount the map is squished depends on the y position. // Sort of account for this by making all boxes a bit bigger. - yStretch = std::pow(_yStretch, 1.3); + yStretch = std::pow(_yStretch, 1.3f); } @@ -80,7 +80,7 @@ float CollisionTile::findPlacementScale(float minPlacementScale, const Point& anchor, const CollisionBox& bo }; } -std::vector CollisionTile::queryRenderedSymbols(const GeometryCoordinates& queryGeometry, const float scale) { +std::vector CollisionTile::queryRenderedSymbols(const GeometryCoordinates& queryGeometry, float scale) { std::vector result; if (queryGeometry.empty()) return result; diff --git a/src/mbgl/text/collision_tile.hpp b/src/mbgl/text/collision_tile.hpp index dbc23b2a798..49b97cb8cb3 100644 --- a/src/mbgl/text/collision_tile.hpp +++ b/src/mbgl/text/collision_tile.hpp @@ -39,10 +39,10 @@ class CollisionTile { public: explicit CollisionTile(PlacementConfig); - float placeFeature(const CollisionFeature&, const bool allowOverlap, const bool avoidEdges); - void insertFeature(CollisionFeature&, const float minPlacementScale, const bool ignorePlacement); + float placeFeature(const CollisionFeature&, bool allowOverlap, bool avoidEdges); + void insertFeature(CollisionFeature&, float minPlacementScale, bool ignorePlacement); - std::vector queryRenderedSymbols(const GeometryCoordinates&, const float scale); + std::vector queryRenderedSymbols(const GeometryCoordinates&, float scale); const PlacementConfig config; From 25faac2155752e81a9562898066112f9f9181681 Mon Sep 17 00:00:00 2001 From: Bruno de Oliveira Abinader Date: Thu, 20 Oct 2016 00:55:25 +0300 Subject: [PATCH 2/7] [core] Avoid duplicating edges for CollisionTile Make 'edges' static to avoid every instance of CollisionTile having its own copy of it. --- src/mbgl/text/collision_tile.cpp | 38 ++++++++++++++------------------ src/mbgl/text/collision_tile.hpp | 1 - 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/src/mbgl/text/collision_tile.cpp b/src/mbgl/text/collision_tile.cpp index 3681581455c..aa83cbae443 100644 --- a/src/mbgl/text/collision_tile.cpp +++ b/src/mbgl/text/collision_tile.cpp @@ -11,20 +11,7 @@ namespace mbgl { -auto infinity = std::numeric_limits::infinity(); - -CollisionTile::CollisionTile(PlacementConfig config_) : config(std::move(config_)), - edges({{ - // left - CollisionBox(Point(0, 0), 0, -infinity, 0, infinity, infinity), - // right - CollisionBox(Point(util::EXTENT, 0), 0, -infinity, 0, infinity, infinity), - // top - CollisionBox(Point(0, 0), -infinity, 0, infinity, 0, infinity), - // bottom - CollisionBox(Point(0, util::EXTENT), -infinity, 0, infinity, 0, infinity), - }}) { - +CollisionTile::CollisionTile(PlacementConfig config_) : config(std::move(config_)) { // Compute the transformation matrix. const float angle_sin = std::sin(config.angle); const float angle_cos = std::cos(config.angle); @@ -81,6 +68,17 @@ float CollisionTile::findPlacementScale(float minPlacementScale, const Point::infinity(); + static const std::array edges {{ + // left + CollisionBox(Point(0, 0), 0, -infinity, 0, infinity, infinity), + // right + CollisionBox(Point(util::EXTENT, 0), 0, -infinity, 0, infinity, infinity), + // top + CollisionBox(Point(0, 0), -infinity, 0, infinity, 0, infinity), + // bottom + CollisionBox(Point(0, util::EXTENT), -infinity, 0, infinity, 0, infinity) + }}; float minPlacementScale = minScale; @@ -98,14 +96,10 @@ float CollisionTile::placeFeature(const CollisionFeature& feature, bool allowOve } if (avoidEdges) { - const Point tl = { box.x1, box.y1 }; - const Point tr = { box.x2, box.y1 }; - const Point bl = { box.x1, box.y2 }; - const Point br = { box.x2, box.y2 }; - const Point rtl = util::matrixMultiply(reverseRotationMatrix, tl); - const Point rtr = util::matrixMultiply(reverseRotationMatrix, tr); - const Point rbl = util::matrixMultiply(reverseRotationMatrix, bl); - const Point rbr = util::matrixMultiply(reverseRotationMatrix, br); + const Point rtl = util::matrixMultiply(reverseRotationMatrix, { box.x1, box.y1 }); + const Point rtr = util::matrixMultiply(reverseRotationMatrix, { box.x2, box.y1 }); + const Point rbl = util::matrixMultiply(reverseRotationMatrix, { box.x1, box.y2 }); + const Point rbr = util::matrixMultiply(reverseRotationMatrix, { box.x2, box.y2 }); CollisionBox rotatedBox(box.anchor, util::min(rtl.x, rtr.x, rbl.x, rbr.x), util::min(rtl.y, rtr.y, rbl.y, rbr.y), diff --git a/src/mbgl/text/collision_tile.hpp b/src/mbgl/text/collision_tile.hpp index 49b97cb8cb3..a2c5220fce9 100644 --- a/src/mbgl/text/collision_tile.hpp +++ b/src/mbgl/text/collision_tile.hpp @@ -52,7 +52,6 @@ class CollisionTile { std::array rotationMatrix; std::array reverseRotationMatrix; - std::array edges; private: float findPlacementScale(float minPlacementScale, From d0b4914bdb6d7b9ca12ee7a202eed53cb2078b60 Mon Sep 17 00:00:00 2001 From: Bruno de Oliveira Abinader Date: Thu, 20 Oct 2016 00:51:33 +0300 Subject: [PATCH 3/7] [core] Simplify CollisionTile::findPlacementScale Improve findPlacementScale semantics by moving the check if the placement scale result is bigger than minimum scale out of the function scope. --- src/mbgl/text/collision_tile.cpp | 12 +++++------- src/mbgl/text/collision_tile.hpp | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/mbgl/text/collision_tile.cpp b/src/mbgl/text/collision_tile.cpp index aa83cbae443..2fea819aaa6 100644 --- a/src/mbgl/text/collision_tile.cpp +++ b/src/mbgl/text/collision_tile.cpp @@ -27,9 +27,8 @@ CollisionTile::CollisionTile(PlacementConfig config_) : config(std::move(config_ } -float CollisionTile::findPlacementScale(float minPlacementScale, const Point& anchor, - const CollisionBox& box, const Point& blockingAnchor, const CollisionBox& blocking) { - +float CollisionTile::findPlacementScale(const Point& anchor, const CollisionBox& box, const Point& blockingAnchor, const CollisionBox& blocking) { + float minPlacementScale = minScale; // Find the lowest scale at which the two boxes can fit side by side without overlapping. // Original algorithm: @@ -90,7 +89,7 @@ float CollisionTile::placeFeature(const CollisionFeature& feature, bool allowOve const CollisionBox& blocking = std::get<1>(*it); Point blockingAnchor = util::matrixMultiply(rotationMatrix, blocking.anchor); - minPlacementScale = findPlacementScale(minPlacementScale, anchor, box, blockingAnchor, blocking); + minPlacementScale = util::max(minPlacementScale, findPlacementScale(anchor, box, blockingAnchor, blocking)); if (minPlacementScale >= maxScale) return minPlacementScale; } } @@ -108,8 +107,7 @@ float CollisionTile::placeFeature(const CollisionFeature& feature, bool allowOve box.maxScale); for (auto& blocking : edges) { - minPlacementScale = findPlacementScale(minPlacementScale, box.anchor, rotatedBox, blocking.anchor, blocking); - + minPlacementScale = util::max(minPlacementScale, findPlacementScale(box.anchor, rotatedBox, blocking.anchor, blocking)); if (minPlacementScale >= maxScale) return minPlacementScale; } } @@ -179,7 +177,7 @@ std::vector CollisionTile::queryRenderedSymbols(const Geometr result.push_back(indexedFeature); } else { auto blockingAnchor = util::matrixMultiply(rotationMatrix, blocking.anchor); - float minPlacementScale = findPlacementScale(minScale, anchor, queryBox, blockingAnchor, blocking); + float minPlacementScale = findPlacementScale(anchor, queryBox, blockingAnchor, blocking); if (minPlacementScale >= scale) { seenFeatures.insert(indexedFeature.index); result.push_back(indexedFeature); diff --git a/src/mbgl/text/collision_tile.hpp b/src/mbgl/text/collision_tile.hpp index a2c5220fce9..186cd19d28b 100644 --- a/src/mbgl/text/collision_tile.hpp +++ b/src/mbgl/text/collision_tile.hpp @@ -54,7 +54,7 @@ class CollisionTile { std::array reverseRotationMatrix; private: - float findPlacementScale(float minPlacementScale, + float findPlacementScale( const Point& anchor, const CollisionBox& box, const Point& blockingAnchor, const CollisionBox& blocking); Box getTreeBox(const Point& anchor, const CollisionBox& box, const float scale = 1.0); From 97c51819b3d47129dc2bf20d7a68c8d320e49339 Mon Sep 17 00:00:00 2001 From: Bruno de Oliveira Abinader Date: Tue, 25 Oct 2016 01:39:33 +0300 Subject: [PATCH 4/7] [core] Added util::polygonIntersectsPolygon --- src/mbgl/util/intersection_tests.cpp | 32 +++++++++++----------------- src/mbgl/util/intersection_tests.hpp | 1 + 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/mbgl/util/intersection_tests.cpp b/src/mbgl/util/intersection_tests.cpp index a0feb8ec37b..c5803572983 100644 --- a/src/mbgl/util/intersection_tests.cpp +++ b/src/mbgl/util/intersection_tests.cpp @@ -16,14 +16,6 @@ bool polygonContainsPoint(const GeometryCoordinates& ring, const GeometryCoordin return c; } -bool multiPolygonContainsPoint(const GeometryCollection& rings, const GeometryCoordinate& p) { - bool c = false; - for (auto& ring : rings) { - c = (c != polygonContainsPoint(ring, p)); - } - return c; -} - // Code from http://stackoverflow.com/a/1501725/331379. float distToSegmentSquared(const GeometryCoordinate& p, const GeometryCoordinate& v, const GeometryCoordinate& w) { if (v == w) return util::distSqr(p, v); @@ -114,23 +106,23 @@ bool polygonIntersectsBufferedMultiLine(const GeometryCoordinates& polygon, cons return false; } -bool polygonIntersectsMultiPolygon(const GeometryCoordinates& polygon, const GeometryCollection& multiPolygon) { - if (polygon.size() == 1) { - return multiPolygonContainsPoint(multiPolygon, polygon.at(0)); +bool polygonIntersectsPolygon(const GeometryCoordinates& polygonA, const GeometryCoordinates& polygonB) { + for (auto& p : polygonA) { + if (polygonContainsPoint(polygonB, p)) return true; } - for (auto& ring : multiPolygon) { - for (auto& p : ring) { - if (polygonContainsPoint(polygon, p)) return true; - } + for (auto& p : polygonB) { + if (polygonContainsPoint(polygonA, p)) return true; } - for (auto& p : polygon) { - if (multiPolygonContainsPoint(multiPolygon, p)) return true; - } + if (lineIntersectsLine(polygonA, polygonB)) return true; - for (auto& polygonB : multiPolygon) { - if (lineIntersectsLine(polygon, polygonB)) return true; + return false; +} + +bool polygonIntersectsMultiPolygon(const GeometryCoordinates& polygon, const GeometryCollection& multiPolygon) { + for (auto& ring : multiPolygon) { + if (polygonIntersectsPolygon(polygon, ring)) return true; } return false; diff --git a/src/mbgl/util/intersection_tests.hpp b/src/mbgl/util/intersection_tests.hpp index 6adb3b5fdfb..5bcb29c7679 100644 --- a/src/mbgl/util/intersection_tests.hpp +++ b/src/mbgl/util/intersection_tests.hpp @@ -7,6 +7,7 @@ namespace util { bool polygonIntersectsBufferedMultiPoint(const GeometryCoordinates&, const GeometryCollection&, float radius); bool polygonIntersectsBufferedMultiLine(const GeometryCoordinates&, const GeometryCollection&, float radius); +bool polygonIntersectsPolygon(const GeometryCoordinates&, const GeometryCoordinates&); bool polygonIntersectsMultiPolygon(const GeometryCoordinates&, const GeometryCollection&); } // namespace util From eee28d92630a86b13533b164648fa5f5ac012e2c Mon Sep 17 00:00:00 2001 From: Bruno de Oliveira Abinader Date: Fri, 21 Oct 2016 16:41:32 +0300 Subject: [PATCH 5/7] [core] Reuse last placement data in CollisionTile::queryRenderedFeatures To obtain precise results, we: 1. Round scale value to obtain same results from symbol shader. 2. Generate a boost geometry polygon to check if it intersects() against all feature boxes. 3. Check if current scale is within each feature's minimum and maximum placement scales. 4. De-scale feature boxes when intersecting to account for the fractional zoom scaling. --- benchmark/fixtures/api/cache.db | Bin 405504 -> 405504 bytes src/mbgl/text/collision_tile.cpp | 100 +++++++++++++++++++++---------- 2 files changed, 68 insertions(+), 32 deletions(-) diff --git a/benchmark/fixtures/api/cache.db b/benchmark/fixtures/api/cache.db index 102c4074ef3f4986c7e36f0be61dd26a7681895a..a62a3b0f00ccfd5bc6d21f6e7472a090dd4f45c5 100644 GIT binary patch delta 804 zcmX|7J#Q015cS^8nP7>W4;;ra0Xw!6C2<_vd^#TvCV-$patBBhmY|@+qDQ)p4jElR zn=UPh<=YZ|g6SyTKtqE>6Ny8|d#5{!dGGD)%-flDX9agw7!@-aHR z{C#us@`v4hY#^Bu#1j*+9v!LD%sGTbLV>VEC`Px0UGH7Cnu|Iju#ht&@V+`ZZ{}Dz zHdF}Pggl|;{mfRYRPCsiYKO2#*e7%e%U;{5EOaPq|A#!_u{nO_oIF(daq-#rv|2tL ze0#nAbpCx{&6%PoBoSsJjB}=($JL+H>2G_UHAyDOjFlUf=)0&Tt3q3?9!pPUwR}=% z_8e<*cTGzfcOT8Z>a1KuVimE5*g$L|>WD2w1Mx^dFRNo)wI)Kpf|i~EUeeMx0-H}2 zwYW@O)-objwH%S}X!*jHt6Ey*l9pf0mnUL^R*)uF;7HzvBe{lQjj*KF;_?lY2ev=p zYuwr&8ubfQfo{GvriPWn44bi8JvQ5l%^H}&gjrL|fZPUimK`la@^vjUK6(R>k#9~! zK)3LFjTE9WX7973EoOwqLntPn6qEAAgOjo9Fc} zua}R0|M2WdEsBYZ5)B-$znea=l8IaHye7CSxa_)-6CUM^ zd;lArQ5Vi6mErOh4!P1d(q~f!W5^2~5l|M2$m>QjSk)^~^X zJ!s_l{f^M_1~ig4(Z*>zyoRPGeAu&CzkAtN9ro?PGW%+%X>}he$Pb}{{K!a+{1~4^ sK7dzvHu_L5{Gmy?w43S%!wkm9*@=WDHQnurZmJu2Y=<9}OCC7?0A-rU#Q*>R diff --git a/src/mbgl/text/collision_tile.cpp b/src/mbgl/text/collision_tile.cpp index 2fea819aaa6..e485fbf36c2 100644 --- a/src/mbgl/text/collision_tile.cpp +++ b/src/mbgl/text/collision_tile.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -135,7 +136,25 @@ void CollisionTile::insertFeature(CollisionFeature& feature, float minPlacementS } +// +---------------------------+ As you zoom, the size of the symbol changes +// |(x1,y1) | | relative to the tile e.g. when zooming in, +// | | | the symbol gets smaller relative to the tile. +// | (x1',y1') v | +// | +-------+-------+ | The boxes inserted into the tree represents +// | | | | | the bounds at the integer zoom level (where +// | | | | | the symbol is biggest relative to the tile). +// | | | | | +// |---->+-------+-------+<----| This happens because placement is updated +// | | |(xa,ya)| | once every new integer zoom level e.g. +// | | | | | std::floor(oldZoom) != std::floor(newZoom). +// | | | | | +// | +-------+-------+ | Thus, they don't represent the exact bounds +// | ^ (x2',y2') | of the symbol at the current zoom level. For +// | | | calculating the bounds at current zoom level +// | | (x2,y2)| we must unscale the box using its center as +// +---------------------------+ transform origin. Box CollisionTile::getTreeBox(const Point& anchor, const CollisionBox& box, const float scale) { + assert(box.x1 <= box.x2 && box.y1 <= box.y2); return Box{ CollisionPoint{ anchor.x + box.x1 / scale, @@ -149,48 +168,65 @@ Box CollisionTile::getTreeBox(const Point& anchor, const CollisionBox& bo } std::vector CollisionTile::queryRenderedSymbols(const GeometryCoordinates& queryGeometry, float scale) { - std::vector result; - if (queryGeometry.empty()) return result; + if (queryGeometry.empty() || (tree.empty() && ignoredTree.empty())) { + return result; + } + // Generate a rotated geometry out of the original query geometry. + // Scale has already been handled by the prior conversions. + GeometryCoordinates polygon; + for (const auto& point : queryGeometry) { + auto rotated = util::matrixMultiply(rotationMatrix, convertPoint(point)); + polygon.push_back(convertPoint(rotated)); + } + + // Predicate for ruling out already seen features. std::unordered_map> sourceLayerFeatures; + auto seenFeature = [&] (const CollisionTreeBox& treeBox) -> bool { + const IndexedSubfeature& feature = std::get<2>(treeBox); + const auto& seenFeatures = sourceLayerFeatures[feature.sourceLayerName]; + return seenFeatures.find(feature.index) == seenFeatures.end(); + }; - mapbox::geometry::multi_point rotatedPoints {}; - rotatedPoints.reserve(queryGeometry.size()); - std::transform(queryGeometry.cbegin(), queryGeometry.cend(), std::back_inserter(rotatedPoints), - [&](const auto& c) { return util::matrixMultiply(rotationMatrix, convertPoint(c)); }); - const auto box = mapbox::geometry::envelope(rotatedPoints); + // Account for the rounding done when updating symbol shader variables. + const float roundedScale = std::pow(2.0f, std::ceil(util::log2(scale) * 10.0f) / 10.0f); - const auto& anchor = box.min; - CollisionBox queryBox(anchor, 0, 0, box.max.x - box.min.x, box.max.y - box.min.y, scale); - auto predicates = bgi::intersects(getTreeBox(anchor, queryBox)); + // Check if feature is rendered (collision free) at current scale. + auto visibleAtScale = [&] (const CollisionTreeBox& treeBox) -> bool { + const CollisionBox& box = std::get<1>(treeBox); + return roundedScale >= box.placementScale && roundedScale <= box.maxScale; + }; + + // Check if query polygon intersects with the feature box at current scale. + auto intersectsAtScale = [&] (const CollisionTreeBox& treeBox) -> bool { + const CollisionBox& collisionBox = std::get<1>(treeBox); + const auto anchor = util::matrixMultiply(rotationMatrix, collisionBox.anchor); + const int16_t x1 = anchor.x + collisionBox.x1 / scale; + const int16_t y1 = anchor.y + collisionBox.y1 / scale * yStretch; + const int16_t x2 = anchor.x + collisionBox.x2 / scale; + const int16_t y2 = anchor.y + collisionBox.y2 / scale * yStretch; + auto bbox = GeometryCoordinates { + { x1, y1 }, { x2, y1 }, { x2, y2 }, { x1, y2 } + }; + return util::polygonIntersectsPolygon(polygon, bbox); + }; - auto fn = [&] (const Tree& tree_, bool ignorePlacement) { + auto predicates = bgi::satisfies(seenFeature) + && bgi::satisfies(visibleAtScale) + && bgi::satisfies(intersectsAtScale); + + auto queryTree = [&](const auto& tree_) { for (auto it = tree_.qbegin(predicates); it != tree_.qend(); ++it) { - const CollisionBox& blocking = std::get<1>(*it); - const IndexedSubfeature& indexedFeature = std::get<2>(*it); - - auto& seenFeatures = sourceLayerFeatures[indexedFeature.sourceLayerName]; - if (seenFeatures.find(indexedFeature.index) == seenFeatures.end()) { - if (ignorePlacement) { - seenFeatures.insert(indexedFeature.index); - result.push_back(indexedFeature); - } else { - auto blockingAnchor = util::matrixMultiply(rotationMatrix, blocking.anchor); - float minPlacementScale = findPlacementScale(anchor, queryBox, blockingAnchor, blocking); - if (minPlacementScale >= scale) { - seenFeatures.insert(indexedFeature.index); - result.push_back(indexedFeature); - } - } - } + const IndexedSubfeature& feature = std::get<2>(*it); + auto& seenFeatures = sourceLayerFeatures[feature.sourceLayerName]; + seenFeatures.insert(feature.index); + result.push_back(feature); } }; - bool ignorePlacement = false; - fn(tree, ignorePlacement); - ignorePlacement = true; - fn(ignoredTree, ignorePlacement); + queryTree(tree); + queryTree(ignoredTree); return result; } From 2dae7201bf317b2b9d37637b423c4bed78e0e3b2 Mon Sep 17 00:00:00 2001 From: Bruno de Oliveira Abinader Date: Sat, 22 Oct 2016 20:09:33 +0300 Subject: [PATCH 6/7] [glfw] Added queryPointAnnotations shortcut --- platform/default/glfw_view.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/platform/default/glfw_view.cpp b/platform/default/glfw_view.cpp index 28789e4a678..9c54fc4ebf6 100644 --- a/platform/default/glfw_view.cpp +++ b/platform/default/glfw_view.cpp @@ -178,7 +178,11 @@ void GLFWView::onKey(GLFWwindow *window, int key, int /*scancode*/, int action, case GLFW_KEY_Z: view->nextOrientation(); break; - case GLFW_KEY_Q: + case GLFW_KEY_Q: { + auto result = view->map->queryPointAnnotations({ {}, { double(view->getSize()[0]), double(view->getSize()[1]) } }); + printf("visible point annotations: %lu\n", result.size()); + } break; + case GLFW_KEY_C: view->clearAnnotations(); break; case GLFW_KEY_P: From 94c02c4c3c558e75a81e4690e8505e369654020a Mon Sep 17 00:00:00 2001 From: Bruno de Oliveira Abinader Date: Sat, 22 Oct 2016 22:13:27 +0300 Subject: [PATCH 7/7] [test] Update mapbox-gl-test-suite hash Now testing 'query-tests/symbol-features-in/pitched-screen'. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 61936e2fe71..de2f6ff39e2 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "lodash": "^4.16.4", "mapbox-gl-shaders": "mapbox/mapbox-gl-shaders#98a56d538b11fb331aa67a6d632d6ecd6821b007", "mapbox-gl-style-spec": "mapbox/mapbox-gl-style-spec#7f62a4fc9f21e619824d68abbc4b03cbc1685572", - "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#6df3b27868cc00432197e3fc9c166fdba7067913", + "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#844ecd23c3314eec509dbcdaf0f956d5504e6fef", "mkdirp": "^0.5.1", "node-cmake": "^1.2.1", "request": "^2.72.0",