Repository navigation
fix querying features that extend across tile boundaries - #6036
Conversation
ChrisLoer
left a comment
There was a problem hiding this comment.
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.
| 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.
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.
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)); |
There was a problem hiding this comment.
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.
| refreshedUponExpiration: boolean; | ||
| reloadCallback: any; | ||
| resourceTiming: ?Array<PerformanceResourceTiming>; | ||
| additionalRadius: number; |
There was a problem hiding this comment.
Maybe rename to something like renderedFeatureBuffer
There was a problem hiding this comment.
"buffer" is like "layer" or "source" -- it means too many things in our code base! Maybe "padding" or "query padding"?
| 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.
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
| this._alignedPosMatrixCache = {}; | ||
| } | ||
|
|
||
| getPitchScaleFactor() { |
There was a problem hiding this comment.
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"?
| type QueryParameters = { | ||
| scale: number, | ||
| bearing: number, | ||
| cameraToCenterDistance: number, |
There was a problem hiding this comment.
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.
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
left a comment
There was a problem hiding this comment.
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
additionalRadiusof the query.Benchmarks
https://bl.ocks.org/anonymous/raw/9762d28049090d90438b81a109918626/
https://bl.ocks.org/anonymous/raw/d69b9f2d20bb2720849c029571929611/
QueryPointis 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