Skip to content

Commit

Permalink
enable lod tile loading for projections (#11129)
Browse files Browse the repository at this point in the history
* enable lod tile loading for projections

to significantly reduce the number of tiles at low zoom levels

* use Math.hypot(...)

Co-authored-by: Vladimir Agafonkin <agafonkin@gmail.com>

* add comments

Co-authored-by: Vladimir Agafonkin <agafonkin@gmail.com>
  • Loading branch information
ansis and mourner authored Oct 18, 2021
1 parent 658ad81 commit 2c08a6e
Showing 1 changed file with 46 additions and 3 deletions.
49 changes: 46 additions & 3 deletions src/geo/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,7 @@ class Transform {
const actualZ = z;

const useElevationData = this.elevation && !options.isTerrainDEM;
const isMercator = this.projection.name === 'mercator';

if (options.minzoom !== undefined && z < options.minzoom) return [];
if (options.maxzoom !== undefined && z > options.maxzoom) z = options.maxzoom;
Expand All @@ -707,13 +708,42 @@ class Transform {
const zoomSplitDistance = this.cameraToCenterDistance / options.tileSize * (options.roundZoom ? 1 : 0.502);

// No change of LOD behavior for pitch lower than 60 and when there is no top padding: return only tile ids from the requested zoom level
const minZoom = this.pitch <= 60.0 && this._edgeInsets.top <= this._edgeInsets.bottom && !this._elevation ? z : 0;
const minZoom = this.pitch <= 60.0 && this._edgeInsets.top <= this._edgeInsets.bottom && !this._elevation && isMercator ? z : 0;

// When calculating tile cover for terrain, create deep AABB for nodes, to ensure they intersect frustum: for sources,
// other than DEM, use minimum of visible DEM tiles and center altitude as upper bound (pitch is always less than 90°).
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 => {
// 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.

// Construct a small square in Mercator coordinates.
const offset = 1 / 40000;
const mcEast = new MercatorCoordinate(mc.x + offset, mc.y, mc.z);
const mcSouth = new MercatorCoordinate(mc.x, mc.y + offset, mc.z);

// Convert the square to projected coordinates.
const ll = mc.toLngLat();
const llEast = mcEast.toLngLat();
const llSouth = mcSouth.toLngLat();
const p = this.locationCoordinate(ll);
const pEast = this.locationCoordinate(llEast);
const pSouth = this.locationCoordinate(llSouth);

// Calculate the size of each edge of the reprojected square
const dx = Math.hypot(pEast.x - p.x, pEast.y - p.y);
const dy = Math.hypot(pSouth.x - p.x, pSouth.y - p.y);

// Calculate the size of a projected square that would have the
// same area as the reprojected square.
return Math.sqrt(dx * dy) / 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 @@ -817,8 +847,21 @@ class Transform {
dzSqr = square(it.aabb.distanceZ(cameraPoint) * meterToTile);
}

let scaleAdjustment = 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;
// Fudge the ratio slightly so that all tiles near the center have the same zoom level.
scaleAdjustment = areaRatio > 0.85 ? 1 : areaRatio;
}

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

return distanceSqr < distToSplitSqr;
Expand Down Expand Up @@ -941,7 +984,7 @@ class Transform {
const cover = result.sort((a, b) => a.distanceSq - b.distanceSq).map(a => a.tileID);
// Relax the assertion on terrain, on high zoom we use distance to center of tile
// while camera might be closer to selected center of map.
assert(!cover.length || this.elevation || cover[0].overscaledZ === overscaledZ);
assert(!cover.length || this.elevation || cover[0].overscaledZ === overscaledZ || !isMercator);
return cover;
}

Expand Down

0 comments on commit 2c08a6e

Please sign in to comment.