Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

implement queryRenderedFeatures #4656

Merged
merged 2 commits into from
Apr 29, 2016
Merged

implement queryRenderedFeatures #4656

merged 2 commits into from
Apr 29, 2016

Conversation

ansis
Copy link
Contributor

@ansis ansis commented Apr 8, 2016

queryRenderedFeatures is mostly implemented. 48/50 query tests are passing. Two are skipped because different constrain behaviour is causing errors.

Questions

  • What should queryRenderedFeatures argument signatures be? js api here
  • What should the return type be? This will use geometry.hpp, right?
  • Should we support filters? Can FilterExpression be made public?

If any of the above are not quick to decide or quick to implement, can we punt and merge this as is? The current external api is not something we want to have people use but it would be good to have this in master with tests running on ci. It's already a huge pull request.

querySourceFeatures is not implemented because it depends heavily on the return type question. It can be done in a separate pr once that's answered.


@jfirebaugh can you review or assign reviewing to someone else?


my other remaining tasks are:

@mb12
Copy link

mb12 commented Apr 8, 2016

@ansis Can you please kindly answer the following?

1.) It looks like all geometries are being inserted in the index by default (irrespective of whether or not the application uses query feature or not)? Is that correct (or not) and intentional? Can tree construction (loadTree call ) for a tile be postponed until the application actually uses query feature?

2.) In 'Source:: Source::queryRenderedFeatures' , under what scenarios would 'tileQueries' have more than one element? We can reduce the number of rtree lookups by picking only one tile per Source.

3.) Is it possible to explain what geometries are actually inserted in the index for symbols(esp. with text)?

@ansis
Copy link
Contributor Author

ansis commented Apr 9, 2016

Thanks for digging in @mb12

1.) It looks like all geometries are being inserted in the index by default (irrespective of whether or not the application uses query feature or not)? Is that correct (or not) and intentional? Can tree construction (loadTree call ) for a tile be postponed until the application actually uses query feature?

Yes, all geometries that are rendered are inserted into the index. It is partially intentional. I need to profile to see if index creation time is significant. One of advantage of doing it at tile load time is that it doesn't block the main thread. On the mapbox-gl-js side we use a different kind of index where insertions are really cheap: https://github.com/mapbox/grid-index

2.) In 'Source:: Source::queryRenderedFeatures' , under what scenarios would 'tileQueries' have more than one element?

When the first argument of queryRenderedFeatures is a box instead of a point

We can reduce the number of rtree lookups by picking only one tile per Source.

This is already done here. Tiles that don't intersect the query box are skipped.

3.) Is it possible to explain what geometries are actually inserted in the index for symbols(esp. with text)?

Symbol querying uses the index that was already created during the symbol placement process. Nothing is inserted into the main FeatureIndex tree. The CollisionTile tree holds a bunch of axis-aligned boxes that are anchored around points. You can render these boxes with MapDebugOptions::Collision.

@@ -174,6 +174,10 @@ class Map : private util::noncopyable {
const char* before = nullptr);
void removeCustomLayer(const std::string& id);

// Feature queries
std::vector<std::string> queryRenderedFeatures(const ScreenCoordinate&, const optional<std::vector<std::string>>& layerIDs = {});
std::vector<std::string> queryRenderedFeatures(const std::vector<ScreenCoordinate>&, const optional<std::vector<std::string>>& layerIDs = {});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const std::vector<ScreenCoordinate>&const std::array<ScreenCoordinate, 2>&

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about the following single-method interface:

    using QueryGeometry = mapbox::util::variant<ScreenCoordinate, std::array<ScreenCoordinate, 2>>;
    std::vector<std::string> queryRenderedFeatures(const QueryGeometry&, const optional<std::vector<std::string>>& layerIDs = {});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, never mind, overloading is better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done d13f7b9

@jfirebaugh
Copy link
Contributor

From what I understand of the abstract algorithm so far, it seems like the simplest natural expression is (pseudocode):

    // do whatever computation is needed for dateline logic, tile cover, etc.
    auto queryRegion = computeQueryRegion(input.query);
    std::vector<result> results;

    for (layer in style.layers) {
        if (layer in input.layers) {
            results.concat(layer.source.queryRenderedFeatures(layer, queryRegion, ...));
        }
    }

    return results;

What fundamentals am I missing, or what would it take to be able to implement it this way?

@jfirebaugh
Copy link
Contributor

As context, I would find it very appealing if this could be implemented as an online algorithm. Not so much for efficiency's sake, just that they tend in the end to be the simplest expression of a lot of algorithms.

@ansis
Copy link
Contributor Author

ansis commented Apr 13, 2016

What fundamentals am I missing, or what would it take to be able to implement it this way?

All features are currently indexed in a single index. For each layer's query you'd be querying the index with the same region again and again and would need to filter out features from all other layers. I haven't tried, but I'm guessing this would be slower. It might be fine though since reading features from the pbf and doing geometry intersections is the expensive part.

Why not index layers individually? They were indexed in a group index earlier and I just stuck with that. I guessed it could be cheaper for queries that cover all layers (the default and worse case) and it could have a lower memory footprint for the transferable grid index that was written for -js. Now that I think about it, it could be fine. Symbols use the existing collision index though, which would need to be split up into separate indexes once parsing is done.

I agree that an online algorithm would be simpler. Do you think it's worth the effort to try to switch?

@1ec5 1ec5 added feature Core The cross-platform C++ core, aka mbgl labels Apr 19, 2016
}

void FeatureIndex::loadTree() {
tree.insert(treeBoxes.begin(), treeBoxes.end());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this loop be made cancellable/interruptible (or not)?
See the comment here:

#2644 (comment)

@ansis
Copy link
Contributor Author

ansis commented Apr 27, 2016

@jfirebaugh I think all your notes are addressed except #4656 (comment) which I think we should leave for later. Can you review the fixes?

@jfirebaugh
Copy link
Contributor

Can you push the rebased / squashed version?

@ansis
Copy link
Contributor Author

ansis commented Apr 27, 2016

squashed version pushed

@jfirebaugh
Copy link
Contributor

👍 Let's merge once CI is passing.

@ansis
Copy link
Contributor Author

ansis commented Apr 27, 2016

CI is passing.

The rtree is really slow when built in Debug mode. Creating the index adds a couple hundred ms. In Release mode it takes 10-15ms. Is this ok, or is this something that needs to be optimized before merging?

@jfirebaugh
Copy link
Contributor

That's somewhat worrisome. Besides being an annoyance for testing a debug build, it's often the case that slow debug performance on a desktop will translate to slow release performance on a mobile device. Can you test this and optimize accordingly?

@mb12
Copy link

mb12 commented Apr 28, 2016

@ansis and @jfirebaugh On mobile devices with use model similar to (Google Maps, Nokia Here, maps.me or Apple Maps) linear search through the geometries should be good enough( query is asynchronous and/or number of queries is too low because of long touch/tap use model).

This is what the use model looks likes:

  • Long touch or tap (Drops a pin or draw a circle around point of query)
  • Panel slides up from bottom or the side
  • The panel gets asynchronously populated

@ansis ansis force-pushed the query-rendered-features branch 2 times, most recently from 3d85d4e to daab075 Compare April 29, 2016 01:08
@ansis
Copy link
Contributor Author

ansis commented Apr 29, 2016

@jfirebaugh I ported GridIndex from -js: 41e6e4b
This should be fast enough now. Can you review that commit?

@mb12 thanks for the suggestion

@ansis ansis force-pushed the query-rendered-features branch 4 times, most recently from 934cbac to 41e6e4b Compare April 29, 2016 01:54
@jfirebaugh jfirebaugh merged commit 700180a into master Apr 29, 2016
@jfirebaugh jfirebaugh deleted the query-rendered-features branch April 29, 2016 19:01
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Core The cross-platform C++ core, aka mbgl feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants