Skip to content

fix: tile renderer recovering the wrong camera position on globe - #8187

Open
Alchez wants to merge 4 commits into
maplibre:mainfrom
Alchez:fix/globe-covering-tiles-camera-position
Open

fix: tile renderer recovering the wrong camera position on globe#8187
Alchez wants to merge 4 commits into
maplibre:mainfrom
Alchez:fix/globe-covering-tiles-camera-position

Conversation

@Alchez

@Alchez Alchez commented Aug 18, 2026

Copy link
Copy Markdown

Related Issues

Problem

On the globe projection, a zoom is decided per tile (after zoom level is > 4) based on the distance between the camera and tile. But sometimes the camera's location is incorrectly computed, which causes tiles for that camera to render instead.

Cause

  • The tile renderer (coveringTiles) is trying to get the camera's (x, y) to decide which tiles to request
  • It does this by going to the screen and coming back:
    1. computing the screen pixel for the ground right below the camera if the map was flat (via getCameraPoint())
    2. asking the projection what that pixel represents on the map (via screenPointToMercatorCoordinate())
  • In most cases, like all of mercator and 0-pitch globe projections, (i) and (ii) agree on where the ground beneath the camera is.
  • The problem is only for pitched globe projections, where (ii)'s returned value is offset from the actual ground below the camera, and so a different camera is assumed.

Proposed Solution

  • Don't go to the screen, and instead ask the transform for the camera directly. Since that doesn't involve the projection, it doesn't matter if it's a mercator or globe.
  • getCameraLngLat already computes the camera this way, but instead of a long-lat, we want the coordinate instead. getCameraLngLat has been restructured to call the transform as well.

Reproduction

JSFiddle: https://jsfiddle.net/auqdp6mk/5/

  • Resize the output screen to view the different canvas sizes reporting different rendered tile count. The worst one I saw was a z5 camera rendering z9 tiles.
  • Moving back and forth between the threshold values should regenerate the wrong number of tiles, and show a drop in FPS.
  • Trying to interact with the globe after render can also show jank based on how many levels it needs to recover from (z9 is worst).
image

Benchmarks

arm before (hz) after (hz) ratio
mercator 193.86 198.43 1.02×
mercator pitched 112.27 114.13 1.02×
globe at 0 pitch 55.01 61.09 1.11×
globe pitched 39.91 34.62 0.87×

The benchmarks are showing correctness instead of a perf gain.

  • The top three results are all at pitch 0, so the delta is only the cost of the call
  • A pitched globe reports slower only because it's actually rendering the correct number of tiles (not a regression)

Launch Checklist

  • Confirm your changes do not include backports from Mapbox projects (unless with compliant license) - if you are not sure about this, please ask!
  • Briefly describe the changes in this PR.
  • Link to related issues.
  • Include before/after visuals or gifs if this PR includes visual changes.
  • Write tests for all new functionality.
  • Add an entry to CHANGELOG.md under the ## main section.
  • Confirm you have read our AI policy here.

Assisted-By: Claude Opus 5

@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.91%. Comparing base (1275d68) to head (6e728e2).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8187      +/-   ##
==========================================
- Coverage   93.91%   93.91%   -0.01%     
==========================================
  Files         290      290              
  Lines       24918    24918              
  Branches     6576     6576              
==========================================
- Hits        23402    23401       -1     
- Misses       1516     1517       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Alchez Alchez changed the title fix(globe): coveringTiles recovering the wrong camera position fix(globe): tile renderer recovering the wrong camera position Aug 18, 2026
@Alchez Alchez changed the title fix(globe): tile renderer recovering the wrong camera position fix: tile renderer recovering the wrong camera position on globe Aug 18, 2026
Comment thread CHANGELOG.md Outdated
Comment thread src/geo/projection/mercator_utils.ts Outdated
Comment thread src/geo/projection/covering_tiles.ts Outdated
Comment thread src/geo/projection/covering_tiles.test.ts Outdated
Comment thread src/geo/projection/covering_tiles.test.ts Outdated
Comment thread src/geo/projection/covering_tiles.test.ts Outdated
@HarelM

HarelM commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Thanks for taking the time to open this PR!
I've added a few comments.

@HarelM

HarelM commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Can you update the initial post with a problem description and a proposed fix (solution explanation). Please make it short.
I've read the code changes but I don't understand why they should solve the problem. So this bit is still not clear from all the text that was written as part of this PR. Sorry...

@Alchez

Alchez commented Aug 19, 2026

Copy link
Copy Markdown
Author

@HarelM no worries, it's slightly hard to wrap my head around it too. I've updated the description, can you see if that helps? I couldn't shorten it too much, so I've atleast tried to use sections to separate the text.

@HarelM

HarelM commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the write-up, it helps to understand.
Is this change somehow related to the following or are they orthogonal?

}

/**
* Returns the camera's own position in mercator coordinates.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think should indicate what it does, but not in a negative form compared to a different method.

It's not clear what's the difference between this on and getCameraPoint and why not use the other one, or why not change the logic of the other one. The explanation should be part of the PR initial post those, not part of this comment.

const frustum = transform.getCameraFrustum();
const plane = transform.getClippingPlane();
const cameraCoord = transform.screenPointToMercatorCoordinate(transform.getCameraPoint());
const cameraCoord = cameraMercatorCoordinate(transform);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

consider renaming this variable to better indicate what it holds.

]);
});

test('far camera at high pitch: no tile is refined past the nominal zoom', () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

how is "refined" related to covering tiles algorithm? I'm a bit confused by this test description.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Are all the test here really needed? You changed a method "outside" the covering tiles method mostly, I would expect to see more tests there instead of here...?

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.

2 participants