CesiumGS / cesium

An open-source JavaScript library for world-class 3D globes and maps :earth_americas:
https://cesium.com/cesiumjs/
Apache License 2.0
12.67k stars 3.44k forks source link

Different level tiles loaded in 2D #4764

Open hpinkos opened 7 years ago

hpinkos commented 7 years ago

Reported on the forum: https://groups.google.com/forum/?hl=en#!topic/cesium-dev/1eIeTqEqgsA

image

  1. http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Hello%20World.html&label=Showcases
  2. Use the base layer picker to select Bing maps roads
  3. Switch to 2D
  4. Gradually zoom in on Europe

Eventually you'll see France/Switzerland tiles from level 5 and Germany/UK will be tiles from level 4. This persists for several scroll wheel clicks for me.

I know this is expected behavior in 3D, but in 2D I would expect all tiles to be from the same level.

pjcozzi commented 7 years ago

I suspect the SSE computation isn't just taking into account the height in 2D.

duvifn commented 7 years ago

The SSE computation for Mercator projection depends on the latitude, here.

You can easily see that this is the reason by temporarily assigning a constant to latitudeFactor variable (for example, 1.0).

cfickler commented 7 years ago

So without fully understanding the getLevelWithMaximumTexelSpacing code, is the issue simply the case the test for a GeographicTilingScheme has a ! surrounding the brackets?

function getLevelWithMaximumTexelSpacing(layer, texelSpacing, latitudeClosestToEquator) { // PERFORMANCE_IDEA: factor out the stuff that doesn't change. var imageryProvider = layer._imageryProvider; var tilingScheme = imageryProvider.tilingScheme; var ellipsoid = tilingScheme.ellipsoid; var latitudeFactor = !(layer._imageryProvider.tilingScheme instanceof GeographicTilingScheme) ? Math.cos(latitudeClosestToEquator) : 1.0;

    var tilingSchemeRectangle = tilingScheme.rectangle;
    var levelZeroMaximumTexelSpacing = ellipsoid.maximumRadius * tilingSchemeRectangle.width * latitudeFactor / (imageryProvider.tileWidth * tilingScheme.getNumberOfXTilesAtLevel(0));

    var twoToTheLevelPower = levelZeroMaximumTexelSpacing / texelSpacing;
    var level = Math.log(twoToTheLevelPower) / Math.log(2);
    var rounded = Math.round(level);

    return rounded| 0;
}

In my case I have the OSM layer which is a WebMercartorTilingScheme which evaluates to true due to the !. So now my WebMercartorTilingScheme is setting the latitudeFactor using the math.cos function instead of setting it to 1.0;

Anyway I found changing this and also setting the default from 1.0 to 0.5 means my map now matches OSM at the same scales.

so the final change is simply:

var latitudeFactor = (layer._imageryProvider.tilingScheme instanceof GeographicTilingScheme) ? Math.cos(latitudeClosestToEquator) : 0.5;

Again, happy to be corrected.

kring commented 7 years ago

No, that's not correct. It will produce incorrect results with an imagery layer using GeographicTilingScheme.

Actually, this is tricky. The original code is correct in 3D, just not in 2D. In 3D, the real world space of a given tile of imagery in the Web Mercator projection gets smaller near the poles. In 2D, this is not the case.

The ideal solution is to separate selection of terrain and imagery tiles. Currently a set of imagery tiles is selected just once for each terrain tile and used from then on. That's a lot of work though.

An easier but not as good solution is to select imagery tiles based on the current scene mode, and when the scene mode changes, redo that computation. This is still pretty tricky. The basic approach is to clear out the imagery list for each loaded terrain tile, and then recreate it by calling that _createTileImagerySkeletons to recreate them according to the rules of the new scene mode. Some issues I can think of:

cfickler commented 7 years ago

ok Kevin, no dramas. I was hoping it was as the 2D map seemed heaps better. This is holding us back from going to Prod :( Canberra streets are completely unreadable

kring commented 7 years ago

As a workaround, just set latitudeFactor to 1.0. In 2D it'll look ok, in 3D it'll load more tiles and go slower, but it shouldn't look terrible, at least.

Or, if I can shamelessly plug my own work, take a look at TerriaJS which uses Leaflet for 2D (still Cesium for 3D). Developed in Australia, too.