-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
Conversation
There was a problem hiding this 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.
src/source/source_cache.js
Outdated
@@ -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; |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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)
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)); |
There was a problem hiding this comment.
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.
src/source/tile.js
Outdated
@@ -91,6 +91,7 @@ class Tile { | |||
refreshedUponExpiration: boolean; | |||
reloadCallback: any; | |||
resourceTiming: ?Array<PerformanceResourceTiming>; | |||
additionalRadius: number; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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"?
There was a problem hiding this comment.
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
src/source/source_cache.js
Outdated
@@ -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; |
There was a problem hiding this comment.
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)
6383b0d
to
aab251b
Compare
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.
aab251b
to
dbe3c23
Compare
src/geo/transform.js
Outdated
@@ -559,6 +559,13 @@ class Transform { | |||
this._posMatrixCache = {}; | |||
this._alignedPosMatrixCache = {}; | |||
} | |||
|
|||
getPitchScaleFactor() { |
There was a problem hiding this comment.
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"?
src/data/feature_index.js
Outdated
|
||
const {FeatureIndexArray} = require('./array_types'); | ||
|
||
type QueryParameters = { | ||
scale: number, | ||
bearing: number, | ||
cameraToCenterDistance: number, |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
There was a problem hiding this 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.
dbe3c23
to
f3f80f3
Compare
|
🎉 |
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
Next steps
cc @lilykaiser