Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix querying features that extend across tile boundaries #6036

Merged
merged 4 commits into from
Mar 12, 2018

Conversation

ansis
Copy link
Contributor

@ansis ansis commented Jan 22, 2018

fix #5756

Some rendered features (circles, symbols, lines) can extend outside of the boundaries of the tile they are stored in. When querying we have to check all tiles within additionalRadius of the query.

Benchmarks

https://bl.ocks.org/anonymous/raw/9762d28049090d90438b81a109918626/
https://bl.ocks.org/anonymous/raw/d69b9f2d20bb2720849c029571929611/

QueryPoint is measurably slower. In order to fix the bug we have to do more work so it makes sense and isn't huge change.

Alternative

I tried a different approach (delaying the short-circuiting until later) that is slightly nicer but a bit slower: 06d2dd3

Launch Checklist

  • briefly describe the changes in this PR
  • write tests for all new functionality
  • [N/A] document any changes to public APIs
  • post benchmark scores
  • [N/A] manually test the debug page

Next steps

  • porting to -native after review

cc @lilykaiser

Copy link
Contributor

@ChrisLoer ChrisLoer left a comment

Choose a reason for hiding this comment

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

Overall approach looks good to me.

I'm not sure if the "undefined additionalRadius" case will actually happen, but if it does maybe just initializing additionalRadius to 0 in the Tile constructor would take care of that.

Am I right that this still doesn't fix the tile boundary issue for symbols, since SymbolStyleLayer#queryRadius always returns 0? It wouldn't be too hard to collect a max tile unit query radius for symbols at layout time, but I don't know how to deal with pitch-scaling. Maybe adjust the query radius based on the largest possible scaling factor (i.e. the pitch scaling applied along the bottom of the tile)?

For that matter, doesn't this also break in pitched maps for circles with circle-pitch-scale: map? circle-pitch-alignment should be fine because we'll err on the conservative side.

@@ -704,14 +704,16 @@ class SourceCache extends Evented {
for (let i = 0; i < ids.length; i++) {
const tile = this._tiles[ids[i]];
const tileID = tile.tileID;
const scale = Math.pow(2, this.transform.zoom - tile.tileID.overscaledZ);
const additionalRadius = tile.additionalRadius * EXTENT / tile.tileSize / scale;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't getIds() return tiles that haven't loaded yet, in which case tile.additionalRadius will be undefined, and this expression will result in NaN?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah - I think it makes sense to initialize Tile#additionalRadius to 0 in the Tile constructor. (And if we weren't going to do that, we'd probably want to type it ?number -- this is a common issue in our flow typing)

this.additionalRadius = 0;
for (const id in this.buckets) {
const bucket = this.buckets[id];
this.additionalRadius = Math.max(this.additionalRadius, painter.style.getLayer(bucket.layerIds[0]).queryRadius(bucket));
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we're storing the additionalRadius value here, can't we re-use the value in Tile#queryRenderedFeature instead of re-calculating? It's probably not that expensive to calculate, but it still seems like a good code simplification.

@@ -91,6 +91,7 @@ class Tile {
refreshedUponExpiration: boolean;
reloadCallback: any;
resourceTiming: ?Array<PerformanceResourceTiming>;
additionalRadius: number;
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe rename to something like renderedFeatureBuffer

Copy link
Contributor

Choose a reason for hiding this comment

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

"buffer" is like "layer" or "source" -- it means too many things in our code base! Maybe "padding" or "query padding"?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ugh you're so right

@@ -704,14 +704,16 @@ class SourceCache extends Evented {
for (let i = 0; i < ids.length; i++) {
const tile = this._tiles[ids[i]];
const tileID = tile.tileID;
const scale = Math.pow(2, this.transform.zoom - tile.tileID.overscaledZ);
const additionalRadius = tile.additionalRadius * EXTENT / tile.tileSize / scale;
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah - I think it makes sense to initialize Tile#additionalRadius to 0 in the Tile constructor. (And if we weren't going to do that, we'd probably want to type it ?number -- this is a common issue in our flow typing)

@jfirebaugh jfirebaugh added this to the 0.45.0 milestone Feb 28, 2018
@ansis ansis force-pushed the fix-circle-query-tile-boundary branch from 6383b0d to aab251b Compare March 6, 2018 21:04
fix #5756

Some rendered features (circles, symbols, lines) can extend outside of
the boundaries of the tile they are stored in. When querying we have to
check all tiles within `additionalRadius` of the query.
`"circle-pitch-scaling": "viewport"` adjsuts circle-radius based on the
distance from the center of the viewport. This incorporates this scaling
into the calculations used by feature querying.
@ansis ansis force-pushed the fix-circle-query-tile-boundary branch from aab251b to dbe3c23 Compare March 6, 2018 23:06
@ansis
Copy link
Contributor Author

ansis commented Mar 6, 2018

I made the suggested changes in ea8a2ee and dbe3c23 and then expanded the scope of the pr by implementing support for circle-pitch-alignment and circle-pitch-scale for querying (
3346dc6, dbe3c23). Would it be better to split off the last two commits into a separate pr?

@@ -559,6 +559,13 @@ class Transform {
this._posMatrixCache = {};
this._alignedPosMatrixCache = {};
}

getPitchScaleFactor() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we could change the name to something like maxPitchScaleFactor along with a comment something along the lines of "returns the pitch scaling factor for the very top of the viewport"?


const {FeatureIndexArray} = require('./array_types');

type QueryParameters = {
scale: number,
bearing: number,
cameraToCenterDistance: number,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we split cameraToCenterDistance out as its own parameter in all these calls? It seems like if we're passing in the transform, we can just get it from the transform when we need it?

const radius = this.paint.get('circle-radius').evaluate(feature) * pixelsToTileUnits;
const stroke = this.paint.get('circle-stroke-width').evaluate(feature) * pixelsToTileUnits;
return multiPolygonIntersectsBufferedMultiPoint(translatedPolygon, geometry, radius + stroke);
const radius = this.paint.get('circle-radius').evaluate(feature);
Copy link
Contributor

Choose a reason for hiding this comment

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

This all looks right to me but it took me a while to reason through it. Would this sort of explanatory comment help?

// For pitch-alignment: map, compare feature geometry to query geometry in the plane of the tile
// Otherwise, compare geometry in the plane of the viewport
// A circle with fixed scaling relative to the viewport gets larger in tile space as it moves into the distance
// A circle with fixed scaling relative to the map gets smaller in viewport space as it moves into the distance

Copy link
Contributor

@ChrisLoer ChrisLoer left a comment

Choose a reason for hiding this comment

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

Yeah this looks good -- just really tricky to reason about!

I have just two requests for name change/added comments and it looks like it might be possible to get rid of passing the cameraToCenterDistance param, but overall it looks ready to me.

I filed #6298 for the remaining work to do on SymbolStyleLayer#queryRadius

`"circle-alignment-scaling": "map"` draws circles on the map plane
instead of the viewport plane. This fixes querying for those circles.
@ansis ansis force-pushed the fix-circle-query-tile-boundary branch from dbe3c23 to f3f80f3 Compare March 12, 2018 15:59
@ansis ansis merged commit b98a9f7 into master Mar 12, 2018
@ansis
Copy link
Contributor Author

ansis commented Mar 12, 2018

  • comment added
  • getPitchFactor renamed to maxPitchScaleFactor
  • redundant cameraToCenterDistance parameter removed

@ansis ansis deleted the fix-circle-query-tile-boundary branch March 12, 2018 16:08
@ChrisLoer
Copy link
Contributor

🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

mouseenter event not firing correctly on circle
4 participants