Skip to content

fix querying features that extend across tile boundaries - #6036

Merged
ansis merged 4 commits into
masterfrom
fix-circle-query-tile-boundary
Mar 12, 2018
Merged

ansis merged 4 commits into
masterfrom
fix-circle-query-tile-boundary

Conversation

@ansis

@ansis ansis commented Jan 22, 2018 •

Copy link
Copy Markdown
Contributor

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

@ChrisLoer ChrisLoer left a comment

Copy link
Copy Markdown
Contributor

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.

Comment thread src/source/source_cache.js Outdated
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
Copy Markdown
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
Copy Markdown
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)

Comment thread src/source/tile.js Outdated
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
Copy Markdown
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.

Comment thread src/source/tile.js Outdated
refreshedUponExpiration: boolean;
reloadCallback: any;
resourceTiming: ?Array<PerformanceResourceTiming>;
additionalRadius: number;

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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

Comment thread src/source/source_cache.js Outdated
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
Copy Markdown
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
ansis added 3 commits March 6, 2018 18:05
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

ansis commented Mar 6, 2018

Copy link
Copy Markdown
Contributor Author

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?

Comment thread src/geo/transform.js Outdated
this._alignedPosMatrixCache = {};
}

getPitchScaleFactor() {

Copy link
Copy Markdown
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"?

Comment thread src/data/feature_index.js Outdated
type QueryParameters = {
scale: number,
bearing: number,
cameraToCenterDistance: number,

Copy link
Copy Markdown
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
Copy Markdown
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

@ChrisLoer ChrisLoer left a comment

Copy link
Copy Markdown
Contributor

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

ansis commented Mar 12, 2018

Copy link
Copy Markdown
Contributor Author
  • 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
Copy Markdown
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