From 300b18d26845e0aac0df6d7b829e06bede047d84 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 15 Oct 2021 20:09:36 -0400 Subject: [PATCH 1/3] enable lod tile loading for projections to significantly reduce the number of tiles at low zoom levels --- src/geo/transform.js | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/geo/transform.js b/src/geo/transform.js index d371e19dfe1..91bfc380de4 100644 --- a/src/geo/transform.js +++ b/src/geo/transform.js @@ -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; @@ -707,13 +708,30 @@ 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 areaAtMercatorCoord = mc => { + 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); + 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); + const dx = Math.sqrt(Math.pow(pEast.x - p.x, 2) + Math.pow(pEast.y - p.y, 2)); + const dy = Math.sqrt(Math.pow(pSouth.x - p.x, 2) + Math.pow(pSouth.y - p.y, 2)); + return Math.sqrt(dx * dy) / offset; + }; + + const centerSize = areaAtMercatorCoord(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; @@ -817,8 +835,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. + const numTiles = Math.pow(2, it.zoom); + const tileCenterSize = areaAtMercatorCoord(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; @@ -941,7 +972,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; } From 3ce93615c716636b1a900be66ad4aba9a3c0abf8 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 18 Oct 2021 10:24:13 -0400 Subject: [PATCH 2/3] use Math.hypot(...) Co-authored-by: Vladimir Agafonkin --- src/geo/transform.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/geo/transform.js b/src/geo/transform.js index 91bfc380de4..865fdbb1675 100644 --- a/src/geo/transform.js +++ b/src/geo/transform.js @@ -725,8 +725,8 @@ class Transform { const p = this.locationCoordinate(ll); const pEast = this.locationCoordinate(llEast); const pSouth = this.locationCoordinate(llSouth); - const dx = Math.sqrt(Math.pow(pEast.x - p.x, 2) + Math.pow(pEast.y - p.y, 2)); - const dy = Math.sqrt(Math.pow(pSouth.x - p.x, 2) + Math.pow(pSouth.y - p.y, 2)); + const dx = Math.hypot(pEast.x - p.x, pEast.y - p.y); + const dy = Math.hypot(pSouth.x - p.x, pSouth.y - p.y); return Math.sqrt(dx * dy) / offset; }; From 4d75c5c1990192915c245cb7a7f3fa553db38194 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 18 Oct 2021 10:45:21 -0400 Subject: [PATCH 3/3] add comments --- src/geo/transform.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/geo/transform.js b/src/geo/transform.js index 865fdbb1675..50b930a391c 100644 --- a/src/geo/transform.js +++ b/src/geo/transform.js @@ -715,22 +715,34 @@ 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 areaAtMercatorCoord = mc => { + 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 = areaAtMercatorCoord(MercatorCoordinate.fromLngLat(this.center)); + const centerSize = sizeAtMercatorCoord(MercatorCoordinate.fromLngLat(this.center)); const aabbForTile = (z, x, y, wrap, min, max) => { const tt = tileTransform({z, x, y}, this.projection); @@ -840,9 +852,9 @@ class Transform { // 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. + // Adjustments are only needed at lower zooms where tiles are not similarly sized. const numTiles = Math.pow(2, it.zoom); - const tileCenterSize = areaAtMercatorCoord(new MercatorCoordinate((it.x + 0.5) / numTiles, (it.y + 0.5) / numTiles)); + 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;