Skip to content

Commit

Permalink
improve lod tile loading for projections (#11225)
Browse files Browse the repository at this point in the history
The previous approach was mistakenly dependent on the center of the
viewport. The intent was to use this as a reference size for what a tile
should be. But at lower zoom levels, if you pan away from the center of
the projection the tiles at the center of the screen may not be the the
ideal size.
  • Loading branch information
ansis authored and SnailBones committed Nov 5, 2021
1 parent 984ac3b commit 9c5807e
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 17 deletions.
17 changes: 10 additions & 7 deletions src/geo/projection/adjustments.js
Expand Up @@ -8,21 +8,24 @@ import type {Projection} from './index.js';
import type Transform from '../transform.js';

export default function getProjectionAdjustments(transform: Transform, withoutRotation?: boolean) {
const projection = transform.projection;

const interpT = getInterpolationT(transform);

const zoomAdjustment = getZoomAdjustment(projection, transform.center);
const zoomAdjustmentOrigin = getZoomAdjustment(projection, LngLat.convert(projection.center));
const scaleAdjustment = Math.pow(2, zoomAdjustment * interpT + (1 - interpT) * zoomAdjustmentOrigin);

const matrix = getShearAdjustment(transform.projection, transform.zoom, transform.center, interpT, withoutRotation);

const scaleAdjustment = getScaleAdjustment(transform);
mat4.scale(matrix, matrix, [scaleAdjustment, scaleAdjustment, 1]);

return matrix;
}

export function getScaleAdjustment(transform: Transform) {
const projection = transform.projection;
const interpT = getInterpolationT(transform);
const zoomAdjustment = getZoomAdjustment(projection, transform.center);
const zoomAdjustmentOrigin = getZoomAdjustment(projection, LngLat.convert(projection.center));
const scaleAdjustment = Math.pow(2, zoomAdjustment * interpT + (1 - interpT) * zoomAdjustmentOrigin);
return scaleAdjustment;
}

export function getProjectionAdjustmentInverted(transform: Transform) {
const m = getProjectionAdjustments(transform, true);
return mat2.invert([], [
Expand Down
19 changes: 9 additions & 10 deletions src/geo/transform.js
Expand Up @@ -14,7 +14,7 @@ import {Aabb, Frustum, Ray} from '../util/primitives.js';
import EdgeInsets from './edge_insets.js';
import {FreeCamera, FreeCameraOptions, orientationFromFrame} from '../ui/free_camera.js';
import assert from 'assert';
import getProjectionAdjustments, {getProjectionAdjustmentInverted} from './projection/adjustments.js';
import getProjectionAdjustments, {getProjectionAdjustmentInverted, getScaleAdjustment} from './projection/adjustments.js';
import {getPixelsToTileUnitsMatrix} from '../source/pixels_to_tile_units.js';

import {UnwrappedTileID, OverscaledTileID, CanonicalTileID} from '../source/tile_id.js';
Expand Down Expand Up @@ -724,7 +724,9 @@ class Transform {
const maxRange = options.isTerrainDEM && this._elevation ? this._elevation.exaggeration() * 10000 : this._centerAltitude;
const minRange = options.isTerrainDEM ? -maxRange : this._elevation ? this._elevation.getMinElevationBelowMSL() : 0;

const sizeAtMercatorCoord = mc => {
const scaleAdjustment = getScaleAdjustment(this);

const relativeScaleAtMercatorCoord = mc => {
// Calculate how scale compares between projected coordinates and mercator coordinates.
// Returns a length. The units don't matter since the result is only
// used in a ratio with other values returned by this function.
Expand All @@ -748,11 +750,9 @@ class Transform {

// Calculate the size of a projected square that would have the
// same area as the reprojected square.
return Math.sqrt(dx * dy) / offset;
return Math.sqrt(dx * dy) * scaleAdjustment / offset;
};

const centerSize = sizeAtMercatorCoord(MercatorCoordinate.fromLngLat(this.center));

const aabbForTile = (z, x, y, wrap, min, max) => {
const tt = tileTransform({z, x, y}, this.projection);
const tx = tt.x / tt.scale;
Expand Down Expand Up @@ -854,21 +854,20 @@ class Transform {
dzSqr = square(it.aabb.distanceZ(cameraPoint) * meterToTile);
}

let scaleAdjustment = 1;
let tileScaleAdjustment = 1;
if (!isMercator && actualZ <= 5) {
// In other projections, not all tiles are the same size.
// Account for the tile size difference by adjusting the distToSplit.
// Adjust by the ratio of the area at the tile center to the area at the map center.
// Adjustments are only needed at lower zooms where tiles are not similarly sized.
const numTiles = Math.pow(2, it.zoom);
const tileCenterSize = sizeAtMercatorCoord(new MercatorCoordinate((it.x + 0.5) / numTiles, (it.y + 0.5) / numTiles));
const areaRatio = tileCenterSize / centerSize;
const relativeScale = relativeScaleAtMercatorCoord(new MercatorCoordinate((it.x + 0.5) / numTiles, (it.y + 0.5) / numTiles));
// Fudge the ratio slightly so that all tiles near the center have the same zoom level.
scaleAdjustment = areaRatio > 0.85 ? 1 : areaRatio;
tileScaleAdjustment = relativeScale > 0.85 ? 1 : relativeScale;
}

const distanceSqr = dx * dx + dy * dy + dzSqr;
const distToSplit = (1 << maxZoom - it.zoom) * zoomSplitDistance * scaleAdjustment;
const distToSplit = (1 << maxZoom - it.zoom) * zoomSplitDistance * tileScaleAdjustment;
const distToSplitSqr = square(distToSplit * distToSplitScale(Math.max(dzSqr, cameraHeightSqr), distanceSqr));

return distanceSqr < distToSplitSqr;
Expand Down

0 comments on commit 9c5807e

Please sign in to comment.