#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace mbgl; using namespace mbgl::style; using namespace mbgl::style::expression; using namespace std::literals; namespace { class QueryTest { public: QueryTest() { map.getStyle().loadJSON(util::read_file("test/fixtures/api/query_style.json")); map.getStyle().addImage(std::make_unique("test-icon", decodeImage(util::read_file("test/fixtures/sprites/default_marker.png")), 1.0)); frontend.render(map); } util::RunLoop loop; std::shared_ptr fileSource = std::make_shared(); ThreadPool threadPool { 4 }; float pixelRatio { 1 }; HeadlessFrontend frontend { pixelRatio, threadPool }; MapAdapter map { frontend, MapObserver::nullObserver(), frontend.getSize(), pixelRatio, fileSource, threadPool, MapOptions().withMapMode(MapMode::Static)}; }; std::vector getTopClusterFeature(QueryTest& test) { test.fileSource->sourceResponse = [&] (const Resource& resource) { EXPECT_EQ("http://url"s, resource.url); Response response; response.data = std::make_unique(util::read_file("test/fixtures/supercluster/places.json"s)); return response; }; LatLng coordinate{0, 0}; GeoJSONOptions options{}; options.cluster = true; auto source = std::make_unique("cluster_source"s, options); source->setURL("http://url"s); source->loadDescription(*test.fileSource); auto clusterLayer = std::make_unique("cluster_layer"s, "cluster_source"s); clusterLayer->setIconImage("test-icon"s); clusterLayer->setIconSize(12.0f); test.map.jumpTo(CameraOptions().withCenter(coordinate).withZoom(0.0)); test.map.getStyle().addSource(std::move(source)); test.map.getStyle().addLayer(std::move(clusterLayer)); test.loop.runOnce(); test.frontend.render(test.map); auto screenCoordinate = test.map.pixelForLatLng(coordinate); const RenderedQueryOptions queryOptions({{{ "cluster_layer"s}}, {}}); return test.frontend.getRenderer()->queryRenderedFeatures(screenCoordinate, queryOptions); } } // end namespace TEST(Query, QueryRenderedFeatures) { QueryTest test; auto features1 = test.frontend.getRenderer()->queryRenderedFeatures(test.map.pixelForLatLng({ 0, 0 })); EXPECT_EQ(features1.size(), 4u); auto features2 = test.frontend.getRenderer()->queryRenderedFeatures(test.map.pixelForLatLng({ 9, 9 })); EXPECT_EQ(features2.size(), 0u); } TEST(Query, QueryRenderedFeaturesFilterLayer) { QueryTest test; auto zz = test.map.pixelForLatLng({ 0, 0 }); auto features1 = test.frontend.getRenderer()->queryRenderedFeatures(zz, {{{ "layer1"}}, {}}); EXPECT_EQ(features1.size(), 1u); auto features2 = test.frontend.getRenderer()->queryRenderedFeatures(zz, {{{ "layer1", "layer2" }}, {}}); EXPECT_EQ(features2.size(), 2u); auto features3 = test.frontend.getRenderer()->queryRenderedFeatures(zz, {{{ "foobar" }}, {}}); EXPECT_EQ(features3.size(), 0u); auto features4 = test.frontend.getRenderer()->queryRenderedFeatures(zz, {{{ "foobar", "layer3" }}, {}}); EXPECT_EQ(features4.size(), 1u); } TEST(Query, QueryRenderedFeaturesFilter) { using namespace mbgl::style::expression::dsl; QueryTest test; auto zz = test.map.pixelForLatLng({ 0, 0 }); const Filter eqFilter(eq(get("key1"), literal("value1"))); auto features1 = test.frontend.getRenderer()->queryRenderedFeatures(zz, {{}, { eqFilter }}); EXPECT_EQ(features1.size(), 1u); const Filter idNotEqFilter(ne(id(), literal("feature1"))); auto features2 = test.frontend.getRenderer()->queryRenderedFeatures(zz, {{{ "layer4" }}, { idNotEqFilter }}); EXPECT_EQ(features2.size(), 0u); const Filter gtFilter(gt(number(get("key2")), literal(1.0))); auto features3 = test.frontend.getRenderer()->queryRenderedFeatures(zz, {{ }, { gtFilter }}); EXPECT_EQ(features3.size(), 1u); } TEST(Query, QuerySourceFeatures) { QueryTest test; auto features1 = test.frontend.getRenderer()->querySourceFeatures("source3"); EXPECT_EQ(features1.size(), 1u); } TEST(Query, QuerySourceFeaturesOptionValidation) { QueryTest test; // GeoJSONSource, doesn't require a layer id auto features = test.frontend.getRenderer()->querySourceFeatures("source3"); ASSERT_EQ(features.size(), 1u); // VectorSource, requires a layer id features = test.frontend.getRenderer()->querySourceFeatures("source5"); ASSERT_EQ(features.size(), 0u); // RasterSource, not supported features = test.frontend.getRenderer()->querySourceFeatures("source6"); ASSERT_EQ(features.size(), 0u); } TEST(Query, QuerySourceFeaturesFilter) { using namespace mbgl::style::expression::dsl; QueryTest test; const Filter eqFilter(eq(get("key1"), literal("value1"))); auto features1 = test.frontend.getRenderer()->querySourceFeatures("source4", {{}, { eqFilter }}); EXPECT_EQ(features1.size(), 1u); const Filter idNotEqFilter(ne(id(), literal("feature1"))); auto features2 = test.frontend.getRenderer()->querySourceFeatures("source4", {{}, { idNotEqFilter }}); EXPECT_EQ(features2.size(), 0u); const Filter gtFilter(gt(number(get("key2")), literal(1.0))); auto features3 = test.frontend.getRenderer()->querySourceFeatures("source4", {{}, { gtFilter }}); EXPECT_EQ(features3.size(), 1u); } TEST(Query, QueryFeatureExtensionsInvalidExtension) { QueryTest test; auto unknownExt = test.frontend.getRenderer()->queryFeatureExtensions("source4"s, {}, "unknown"s, "children"s); auto unknownValue = unknownExt.get(); EXPECT_TRUE(unknownValue.is()); } TEST(Query, QueryFeatureExtensionsSuperclusterChildren) { QueryTest test; auto topClusterFeature = getTopClusterFeature(test); EXPECT_EQ(topClusterFeature.size(), 1u); const auto featureProps = topClusterFeature[0].properties; auto clusterId = featureProps.find("cluster_id"s); auto cluster = featureProps.find("cluster"s); EXPECT_TRUE(clusterId != featureProps.end()); EXPECT_TRUE(cluster != featureProps.end()); auto queryChildren = test.frontend.getRenderer()->queryFeatureExtensions("cluster_source"s, topClusterFeature[0], "supercluster"s, "children"s); EXPECT_TRUE(queryChildren.is()); auto children = queryChildren.get(); EXPECT_EQ(children.size(), 4u); // Compare results produced by supercluster with default clustering options. EXPECT_EQ(children[0].properties["cluster_id"].get(), 2u); EXPECT_EQ(children[1].properties["cluster_id"].get(), 34u); EXPECT_EQ(children[2].properties["cluster_id"].get(), 258u); EXPECT_EQ(children[3].properties["cluster_id"].get(), 2466u); EXPECT_EQ(children[0].properties["point_count"].get(), 7u); EXPECT_EQ(children[1].properties["point_count"].get(), 16u); EXPECT_EQ(children[2].properties["point_count"].get(), 7u); EXPECT_EQ(children[3].properties["point_count"].get(), 2u); } TEST(Query, QueryFeatureExtensionsSuperclusterExpansionZoom) { QueryTest test; auto topClusterFeature = getTopClusterFeature(test); EXPECT_EQ(topClusterFeature.size(), 1u); auto queryChildren = test.frontend.getRenderer()->queryFeatureExtensions("cluster_source"s, topClusterFeature[0], "supercluster"s, "children"s); auto children = queryChildren.get(); auto queryExpansionZoom1 = test.frontend.getRenderer()->queryFeatureExtensions("cluster_source"s, topClusterFeature[0], "supercluster"s, "expansion-zoom"s); auto queryExpansionZoom2 = test.frontend.getRenderer()->queryFeatureExtensions("cluster_source"s, children[3], "supercluster"s, "expansion-zoom"s); auto zoomValue1 = queryExpansionZoom1.get(); auto zoomValue2 = queryExpansionZoom2.get(); EXPECT_TRUE(zoomValue1.is()); EXPECT_TRUE(zoomValue2.is()); EXPECT_EQ(zoomValue1.get(), 1u); EXPECT_EQ(zoomValue2.get(), 3u); } TEST(Query, QueryFeatureExtensionsSuperclusterLeaves) { QueryTest test; auto topClusterFeature = getTopClusterFeature(test); EXPECT_EQ(topClusterFeature.size(), 1u); // Get leaves for cluster 1, with default limit 10, offset 0. auto queryClusterLeaves = test.frontend.getRenderer()->queryFeatureExtensions("cluster_source"s, topClusterFeature[0], "supercluster"s, "leaves"s); EXPECT_TRUE(queryClusterLeaves.is()); auto leaves = queryClusterLeaves.get(); EXPECT_EQ(leaves.size(), 10u); // Get leaves for cluster 1, with limit 3, offset 0. const std::map limitOpts = { {"limit"s, static_cast(3u)} }; auto queryClusterLeavesLimit3 = test.frontend.getRenderer()->queryFeatureExtensions("cluster_source"s, topClusterFeature[0], "supercluster"s, "leaves"s, limitOpts); auto limitLeaves3 = queryClusterLeavesLimit3.get(); EXPECT_EQ(limitLeaves3.size(), 3u); EXPECT_EQ(limitLeaves3[0].properties["name"].get(), "Niagara Falls"s); EXPECT_EQ(limitLeaves3[1].properties["name"].get(), "Cape May"s); EXPECT_EQ(limitLeaves3[2].properties["name"].get(), "Cape Fear"s); // Get leaves for cluster 1, with limit 3, offset 3. const std::map offsetOpts = { {"limit"s, static_cast(3u)}, {"offset"s, static_cast(3u)} }; auto queryClusterLeavesOffset3 = test.frontend.getRenderer()->queryFeatureExtensions("cluster_source"s, topClusterFeature[0], "supercluster"s, "leaves"s, offsetOpts); auto offsetLeaves3 = queryClusterLeavesOffset3.get(); EXPECT_EQ(offsetLeaves3.size(), 3u); EXPECT_EQ(offsetLeaves3[0].properties["name"].get(), "Cape Hatteras"s); EXPECT_EQ(offsetLeaves3[1].properties["name"].get(), "Cape Sable"s); EXPECT_EQ(offsetLeaves3[2].properties["name"].get(), "Cape Cod"s); }