From 1cf722c0e106e0cf9ec4c30956d6d3d3adc5c3ae Mon Sep 17 00:00:00 2001 From: Stepan Kuzmin Date: Fri, 7 Oct 2022 18:15:46 +0300 Subject: [PATCH 01/10] Recursively delve into midpoints to calculate bounds on globe --- debug/globe-bounds.html | 36 +++++++++++++++++--- src/geo/transform.js | 75 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 5 deletions(-) diff --git a/debug/globe-bounds.html b/debug/globe-bounds.html index f0aa77fb31f..810ec8b87ec 100644 --- a/debug/globe-bounds.html +++ b/debug/globe-bounds.html @@ -46,6 +46,10 @@ 'type': 'LineString', coordinates: [] }; +var globeBoundsData = { + 'type': 'LineString', + coordinates: [] +}; map2.on('load', () => { map2.addSource('bounds', { @@ -62,20 +66,34 @@ } }); - map2.addSource('abounds', { + map2.addSource('red-bounds', { 'type': 'geojson', 'data': boundsData }); map2.addLayer({ - 'id': 'abounds', + 'id': 'red-bounds', 'type': 'line', - 'source': 'abounds', + 'source': 'red-bounds', 'paint': { 'line-color': 'red', 'line-width': 5 } }); + map2.addSource('green-bounds', { + 'type': 'geojson', + 'data': globeBoundsData + }); + map2.addLayer({ + 'id': 'green-bounds', + 'type': 'line', + 'source': 'green-bounds', + 'paint': { + 'line-color': 'green', + 'line-width': 5 + } + }); + updateBounds(); }); @@ -96,6 +114,7 @@ lineData.coordinates = points.map(p => map.unproject(p).toArray()); const bounds = map.getBounds(); + const globeBounds = map.transform._getGlobeBounds(); boundsData.coordinates = [ bounds.getSouthWest().toArray(), @@ -105,8 +124,17 @@ bounds.getSouthWest().toArray(), ]; + globeBoundsData.coordinates = [ + globeBounds.getSouthWest().toArray(), + globeBounds.getSouthEast().toArray(), + globeBounds.getNorthEast().toArray(), + globeBounds.getNorthWest().toArray(), + globeBounds.getSouthWest().toArray(), + ]; + map2.getSource('bounds').setData(lineData); - map2.getSource('abounds').setData(boundsData); + map2.getSource('red-bounds').setData(boundsData); + map2.getSource('green-bounds').setData(globeBoundsData); } const marker = new mapboxgl.Marker(); diff --git a/src/geo/transform.js b/src/geo/transform.js index 5f19dc3dbd5..e291fe427c0 100644 --- a/src/geo/transform.js +++ b/src/geo/transform.js @@ -2,7 +2,7 @@ import LngLat from './lng_lat.js'; import LngLatBounds from './lng_lat_bounds.js'; -import MercatorCoordinate, {mercatorXfromLng, mercatorYfromLat, mercatorZfromAltitude, latFromMercatorY, MAX_MERCATOR_LATITUDE, circumferenceAtLatitude} from './mercator_coordinate.js'; +import MercatorCoordinate, {mercatorXfromLng, mercatorYfromLat, mercatorZfromAltitude, lngFromMercatorX, latFromMercatorY, MAX_MERCATOR_LATITUDE, circumferenceAtLatitude} from './mercator_coordinate.js'; import {getProjection} from './projection/index.js'; import {tileAABB} from '../geo/projection/tile_transform.js'; import Point from '@mapbox/point-geometry'; @@ -1344,6 +1344,79 @@ class Transform { new Point(Number.MAX_VALUE, Number.MAX_VALUE); } + _getGlobeBounds(min: number = 0, max: number = 0): LngLatBounds { + const topLeft = new Point(this._edgeInsets.left, this._edgeInsets.top); + const topRight = new Point(this.width - this._edgeInsets.right, this._edgeInsets.top); + const bottomRight = new Point(this.width - this._edgeInsets.right, this.height - this._edgeInsets.bottom); + const bottomLeft = new Point(this._edgeInsets.left, this.height - this._edgeInsets.bottom); + + // Consider far points at the maximum possible elevation + // and near points at the minimum to ensure full coverage. + const tl = this.pointCoordinate(topLeft, min); + const tr = this.pointCoordinate(topRight, min); + const br = this.pointCoordinate(bottomRight, max); + const bl = this.pointCoordinate(bottomLeft, max); + + const projection = this.projection; + const s = Math.pow(2, -this.zoom); + + const x1 = Math.min(tl.x, bl.x); + const x2 = Math.max(tr.x, br.x); + const y1 = Math.min(tl.y, tr.y); + const y2 = Math.max(bl.y, br.y); + + const lng1 = lngFromMercatorX(x1); + const lng2 = lngFromMercatorX(x2); + const lat1 = latFromMercatorY(y1); + const lat2 = latFromMercatorY(y2); + + const p0 = projection.project(lng1, lat1); + const p1 = projection.project(lng2, lat1); + const p2 = projection.project(lng2, lat2); + const p3 = projection.project(lng1, lat2); + + let minX = Math.min(p0.x, p1.x, p2.x, p3.x); + let minY = Math.min(p0.y, p1.y, p2.y, p3.y); + let maxX = Math.max(p0.x, p1.x, p2.x, p3.x); + let maxY = Math.max(p0.y, p1.y, p2.y, p3.y); + + // we pick an error threshold for calculating the bbox that balances between performance and precision + const maxErr = s / 16; + + function processSegment(pa, pb, ax, ay, bx, by) { + const mx = (ax + bx) / 2; + const my = (ay + by) / 2; + + const pm = projection.project(lngFromMercatorX(mx), latFromMercatorY(my)); + const err = Math.max(0, minX - pm.x, minY - pm.y, pm.x - maxX, pm.y - maxY); + + minX = Math.min(minX, pm.x); + maxX = Math.max(maxX, pm.x); + minY = Math.min(minY, pm.y); + maxY = Math.max(maxY, pm.y); + + if (err > maxErr) { + processSegment(pa, pm, ax, ay, mx, my); + processSegment(pm, pb, mx, my, bx, by); + } + } + + processSegment(p0, p1, x1, y1, x2, y1); + processSegment(p1, p2, x2, y1, x2, y2); + processSegment(p2, p3, x2, y2, x1, y2); + processSegment(p3, p0, x1, y2, x1, y1); + + // extend the bbox by max error to make sure coords don't go past tile extent + minX -= maxErr; + minY -= maxErr; + maxX += maxErr; + maxY += maxErr; + + const sw = new LngLat(lngFromMercatorX(minX), latFromMercatorY(minY)); + const ne = new LngLat(lngFromMercatorX(maxX), latFromMercatorY(maxY)); + return new LngLatBounds(sw, ne); + } + _getBounds(min: number, max: number): LngLatBounds { const topLeft = new Point(this._edgeInsets.left, this._edgeInsets.top); const topRight = new Point(this.width - this._edgeInsets.right, this._edgeInsets.top); From 675f5c76e47a7188a8c8eac9cd8baaf7716f47c9 Mon Sep 17 00:00:00 2001 From: Stepan Kuzmin Date: Wed, 12 Oct 2022 16:49:53 +0300 Subject: [PATCH 02/10] Use raycasting to get points on Globe --- src/geo/transform.js | 50 ++++++++++++-------------------------------- 1 file changed, 13 insertions(+), 37 deletions(-) diff --git a/src/geo/transform.js b/src/geo/transform.js index e291fe427c0..fa88a9d3263 100644 --- a/src/geo/transform.js +++ b/src/geo/transform.js @@ -20,7 +20,7 @@ import {UnwrappedTileID, OverscaledTileID, CanonicalTileID} from '../source/tile import {calculateGlobeMatrix, GLOBE_ZOOM_THRESHOLD_MIN, GLOBE_SCALE_MATCH_LATITUDE} from '../geo/projection/globe_util.js'; import {projectClamped} from '../symbol/projection.js'; -import type Projection from '../geo/projection/projection.js'; +import type Projection, {ProjectedPoint} from '../geo/projection/projection.js'; import type {Elevation} from '../terrain/elevation.js'; import type {PaddingOptions} from './edge_insets.js'; import type Tile from '../source/tile.js'; @@ -1350,44 +1350,20 @@ class Transform { const bottomRight = new Point(this.width - this._edgeInsets.right, this.height - this._edgeInsets.bottom); const bottomLeft = new Point(this._edgeInsets.left, this.height - this._edgeInsets.bottom); - // Consider far points at the maximum possible elevation - // and near points at the minimum to ensure full coverage. - const tl = this.pointCoordinate(topLeft, min); - const tr = this.pointCoordinate(topRight, min); - const br = this.pointCoordinate(bottomRight, max); - const bl = this.pointCoordinate(bottomLeft, max); - - const projection = this.projection; - const s = Math.pow(2, -this.zoom); - - const x1 = Math.min(tl.x, bl.x); - const x2 = Math.max(tr.x, br.x); - const y1 = Math.min(tl.y, tr.y); - const y2 = Math.max(bl.y, br.y); - - const lng1 = lngFromMercatorX(x1); - const lng2 = lngFromMercatorX(x2); - const lat1 = latFromMercatorY(y1); - const lat2 = latFromMercatorY(y2); - - const p0 = projection.project(lng1, lat1); - const p1 = projection.project(lng2, lat1); - const p2 = projection.project(lng2, lat2); - const p3 = projection.project(lng1, lat2); - - let minX = Math.min(p0.x, p1.x, p2.x, p3.x); - let minY = Math.min(p0.y, p1.y, p2.y, p3.y); - let maxX = Math.max(p0.x, p1.x, p2.x, p3.x); - let maxY = Math.max(p0.y, p1.y, p2.y, p3.y); + const [[lng1, lat1], [lng2, lat2]] = this._getBounds(min, max).toArray(); + let {x: minX, y: maxY} = this.projection.project(lng1, lat1); + let {x: maxX, y: minY} = this.projection.project(lng2, lat2); // we pick an error threshold for calculating the bbox that balances between performance and precision + const s = Math.pow(2, -this.zoom); const maxErr = s / 16; - function processSegment(pa, pb, ax, ay, bx, by) { + const processSegment = (pa, pb, ax, ay, bx, by) => { const mx = (ax + bx) / 2; const my = (ay + by) / 2; - const pm = projection.project(lngFromMercatorX(mx), latFromMercatorY(my)); + const {x, y} = this.pointCoordinate3D(new Point(mx, my)); + const pm = this.projection.project(lngFromMercatorX(x), latFromMercatorY(y)); const err = Math.max(0, minX - pm.x, minY - pm.y, pm.x - maxX, pm.y - maxY); minX = Math.min(minX, pm.x); @@ -1399,12 +1375,12 @@ class Transform { processSegment(pa, pm, ax, ay, mx, my); processSegment(pm, pb, mx, my, bx, by); } - } + }; - processSegment(p0, p1, x1, y1, x2, y1); - processSegment(p1, p2, x2, y1, x2, y2); - processSegment(p2, p3, x2, y2, x1, y2); - processSegment(p3, p0, x1, y2, x1, y1); + processSegment(topLeft, topRight, topLeft.x, topLeft.y, topRight.x, topRight.y); + processSegment(topRight, bottomRight, topRight.x, topRight.y, bottomRight.x, bottomRight.y); + processSegment(bottomRight, bottomLeft, bottomRight.x, bottomRight.y, bottomLeft.x, bottomLeft.y); + processSegment(bottomLeft, topLeft, bottomLeft.x, bottomLeft.y, topLeft.x, topLeft.y); // extend the bbox by max error to make sure coords don't go past tile extent minX -= maxErr; From 26c42377ada4a5137bd1c6c6e7ea0b4f659c6a5e Mon Sep 17 00:00:00 2001 From: Stepan Kuzmin Date: Thu, 13 Oct 2022 15:22:32 +0300 Subject: [PATCH 03/10] Don't use the Mercator bounds when calculating Globe bounds --- src/geo/transform.js | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/geo/transform.js b/src/geo/transform.js index fa88a9d3263..714bed2d9b0 100644 --- a/src/geo/transform.js +++ b/src/geo/transform.js @@ -1344,26 +1344,32 @@ class Transform { new Point(Number.MAX_VALUE, Number.MAX_VALUE); } - _getGlobeBounds(min: number = 0, max: number = 0): LngLatBounds { + _getGlobeBounds(): LngLatBounds { const topLeft = new Point(this._edgeInsets.left, this._edgeInsets.top); const topRight = new Point(this.width - this._edgeInsets.right, this._edgeInsets.top); const bottomRight = new Point(this.width - this._edgeInsets.right, this.height - this._edgeInsets.bottom); const bottomLeft = new Point(this._edgeInsets.left, this.height - this._edgeInsets.bottom); - const [[lng1, lat1], [lng2, lat2]] = this._getBounds(min, max).toArray(); - let {x: minX, y: maxY} = this.projection.project(lng1, lat1); - let {x: maxX, y: minY} = this.projection.project(lng2, lat2); + const tl = this.pointCoordinate3D(topLeft); + const tr = this.pointCoordinate3D(topRight); + const br = this.pointCoordinate3D(bottomRight); + const bl = this.pointCoordinate3D(bottomLeft); + + let minX = Math.min(tl.x, bl.x); + let maxX = Math.max(tr.x, br.x); + let minY = Math.min(tl.y, tr.y); + let maxY = Math.max(bl.y, br.y); // we pick an error threshold for calculating the bbox that balances between performance and precision const s = Math.pow(2, -this.zoom); const maxErr = s / 16; - const processSegment = (pa, pb, ax, ay, bx, by) => { + const processSegment = (ax, ay, bx, by) => { const mx = (ax + bx) / 2; const my = (ay + by) / 2; - const {x, y} = this.pointCoordinate3D(new Point(mx, my)); - const pm = this.projection.project(lngFromMercatorX(x), latFromMercatorY(y)); + const p = new Point(mx, my); + const pm = this.pointCoordinate3D(p); const err = Math.max(0, minX - pm.x, minY - pm.y, pm.x - maxX, pm.y - maxY); minX = Math.min(minX, pm.x); @@ -1372,21 +1378,15 @@ class Transform { maxY = Math.max(maxY, pm.y); if (err > maxErr) { - processSegment(pa, pm, ax, ay, mx, my); - processSegment(pm, pb, mx, my, bx, by); + processSegment(ax, ay, mx, my); + processSegment(mx, my, bx, by); } }; - processSegment(topLeft, topRight, topLeft.x, topLeft.y, topRight.x, topRight.y); - processSegment(topRight, bottomRight, topRight.x, topRight.y, bottomRight.x, bottomRight.y); - processSegment(bottomRight, bottomLeft, bottomRight.x, bottomRight.y, bottomLeft.x, bottomLeft.y); - processSegment(bottomLeft, topLeft, bottomLeft.x, bottomLeft.y, topLeft.x, topLeft.y); - - // extend the bbox by max error to make sure coords don't go past tile extent - minX -= maxErr; - minY -= maxErr; - maxX += maxErr; - maxY += maxErr; + processSegment(topLeft.x, topLeft.y, topRight.x, topRight.y); + processSegment(topRight.x, topRight.y, bottomRight.x, bottomRight.y); + processSegment(bottomRight.x, bottomRight.y, bottomLeft.x, bottomLeft.y); + processSegment(bottomLeft.x, bottomLeft.y, topLeft.x, topLeft.y); const sw = new LngLat(lngFromMercatorX(minX), latFromMercatorY(minY)); const ne = new LngLat(lngFromMercatorX(maxX), latFromMercatorY(maxY)); From 0923db2a82da6c66dd623f96b0d1c7f45ecbdf5b Mon Sep 17 00:00:00 2001 From: Stepan Kuzmin Date: Thu, 13 Oct 2022 15:33:54 +0300 Subject: [PATCH 04/10] Calculate the globe bounds in getBounds if the globe is enabled --- src/geo/transform.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/geo/transform.js b/src/geo/transform.js index 714bed2d9b0..a772ec167eb 100644 --- a/src/geo/transform.js +++ b/src/geo/transform.js @@ -20,7 +20,7 @@ import {UnwrappedTileID, OverscaledTileID, CanonicalTileID} from '../source/tile import {calculateGlobeMatrix, GLOBE_ZOOM_THRESHOLD_MIN, GLOBE_SCALE_MATCH_LATITUDE} from '../geo/projection/globe_util.js'; import {projectClamped} from '../symbol/projection.js'; -import type Projection, {ProjectedPoint} from '../geo/projection/projection.js'; +import type Projection from '../geo/projection/projection.js'; import type {Elevation} from '../terrain/elevation.js'; import type {PaddingOptions} from './edge_insets.js'; import type Tile from '../source/tile.js'; @@ -1394,6 +1394,10 @@ class Transform { } _getBounds(min: number, max: number): LngLatBounds { + if (this.projection.name === 'globe') { + return this._getGlobeBounds(); + } + const topLeft = new Point(this._edgeInsets.left, this._edgeInsets.top); const topRight = new Point(this.width - this._edgeInsets.right, this._edgeInsets.top); const bottomRight = new Point(this.width - this._edgeInsets.right, this.height - this._edgeInsets.bottom); From 12b2a376f59d934b9a0840d9027c812afa185a26 Mon Sep 17 00:00:00 2001 From: Stepan Kuzmin Date: Thu, 13 Oct 2022 17:25:12 +0300 Subject: [PATCH 05/10] Add globe bounds unit test --- test/unit/ui/map.test.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/unit/ui/map.test.js b/test/unit/ui/map.test.js index d1ac3ff29bb..c220a0307cd 100755 --- a/test/unit/ui/map.test.js +++ b/test/unit/ui/map.test.js @@ -1426,6 +1426,27 @@ test('Map', (t) => { t.end(); }); + t.test('globe bounds', (t) => { + const map = createMap(t, {zoom: 0, skipCSSStub: true}); + const mercatorBounds = map.getBounds(); + + t.same( + toFixed(mercatorBounds.toArray()), + toFixed([[ -70.3125000000, -57.3265212252, ], [ 70.3125000000, 57.3265212252]]) + ); + + map.setProjection('globe'); + t.stub(console, 'warn'); + const globeBounds = map.getBounds(); + + t.same( + toFixed(globeBounds.toArray()), + toFixed([[ -73.8873304141, 73.8873304141, ], [ 73.8873304141, -73.8873304141]]) + ); + + t.end(); + }); + t.end(); function toFixed(bounds) { From 3905a9fb3480ca0aa38cb43fcd0a27f63c2620ca Mon Sep 17 00:00:00 2001 From: Stepan Kuzmin Date: Thu, 13 Oct 2022 17:27:08 +0300 Subject: [PATCH 06/10] Revert debug page --- debug/globe-bounds.html | 36 ++++-------------------------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/debug/globe-bounds.html b/debug/globe-bounds.html index 810ec8b87ec..f0aa77fb31f 100644 --- a/debug/globe-bounds.html +++ b/debug/globe-bounds.html @@ -46,10 +46,6 @@ 'type': 'LineString', coordinates: [] }; -var globeBoundsData = { - 'type': 'LineString', - coordinates: [] -}; map2.on('load', () => { map2.addSource('bounds', { @@ -66,34 +62,20 @@ } }); - map2.addSource('red-bounds', { + map2.addSource('abounds', { 'type': 'geojson', 'data': boundsData }); map2.addLayer({ - 'id': 'red-bounds', + 'id': 'abounds', 'type': 'line', - 'source': 'red-bounds', + 'source': 'abounds', 'paint': { 'line-color': 'red', 'line-width': 5 } }); - map2.addSource('green-bounds', { - 'type': 'geojson', - 'data': globeBoundsData - }); - map2.addLayer({ - 'id': 'green-bounds', - 'type': 'line', - 'source': 'green-bounds', - 'paint': { - 'line-color': 'green', - 'line-width': 5 - } - }); - updateBounds(); }); @@ -114,7 +96,6 @@ lineData.coordinates = points.map(p => map.unproject(p).toArray()); const bounds = map.getBounds(); - const globeBounds = map.transform._getGlobeBounds(); boundsData.coordinates = [ bounds.getSouthWest().toArray(), @@ -124,17 +105,8 @@ bounds.getSouthWest().toArray(), ]; - globeBoundsData.coordinates = [ - globeBounds.getSouthWest().toArray(), - globeBounds.getSouthEast().toArray(), - globeBounds.getNorthEast().toArray(), - globeBounds.getNorthWest().toArray(), - globeBounds.getSouthWest().toArray(), - ]; - map2.getSource('bounds').setData(lineData); - map2.getSource('red-bounds').setData(boundsData); - map2.getSource('green-bounds').setData(globeBoundsData); + map2.getSource('abounds').setData(boundsData); } const marker = new mapboxgl.Marker(); From 4e75ff8dc79b6be2363d0bf584cdf0fa73cd2413 Mon Sep 17 00:00:00 2001 From: Stepan Kuzmin Date: Fri, 14 Oct 2022 11:07:27 +0300 Subject: [PATCH 07/10] Update src/geo/transform.js Co-authored-by: Volodymyr Agafonkin --- src/geo/transform.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/geo/transform.js b/src/geo/transform.js index a772ec167eb..492391609f2 100644 --- a/src/geo/transform.js +++ b/src/geo/transform.js @@ -1345,10 +1345,9 @@ class Transform { } _getGlobeBounds(): LngLatBounds { - const topLeft = new Point(this._edgeInsets.left, this._edgeInsets.top); - const topRight = new Point(this.width - this._edgeInsets.right, this._edgeInsets.top); - const bottomRight = new Point(this.width - this._edgeInsets.right, this.height - this._edgeInsets.bottom); - const bottomLeft = new Point(this._edgeInsets.left, this.height - this._edgeInsets.bottom); + const {top, left} = this._edgeInsets; + const bottom = this.height - this._edgeInsets.bottom; + const right = this.width - this._edgeInsets.right; const tl = this.pointCoordinate3D(topLeft); const tr = this.pointCoordinate3D(topRight); From 796c8202141f2e2dcb3f21145bb8e37c3e87963b Mon Sep 17 00:00:00 2001 From: Stepan Kuzmin Date: Fri, 14 Oct 2022 12:06:07 +0300 Subject: [PATCH 08/10] Add comment on error metric --- src/geo/transform.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/geo/transform.js b/src/geo/transform.js index 492391609f2..4130948eb45 100644 --- a/src/geo/transform.js +++ b/src/geo/transform.js @@ -1349,10 +1349,10 @@ class Transform { const bottom = this.height - this._edgeInsets.bottom; const right = this.width - this._edgeInsets.right; - const tl = this.pointCoordinate3D(topLeft); - const tr = this.pointCoordinate3D(topRight); - const br = this.pointCoordinate3D(bottomRight); - const bl = this.pointCoordinate3D(bottomLeft); + const tl = this.pointCoordinate3D(new Point(left, top)); + const tr = this.pointCoordinate3D(new Point(right, top)); + const br = this.pointCoordinate3D(new Point(right, bottom)); + const bl = this.pointCoordinate3D(new Point(left, bottom)); let minX = Math.min(tl.x, bl.x); let maxX = Math.max(tr.x, br.x); @@ -1369,6 +1369,9 @@ class Transform { const p = new Point(mx, my); const pm = this.pointCoordinate3D(p); + + // The error metric is the maximum distance between the midpoint + // and each of the currently calculated bounds const err = Math.max(0, minX - pm.x, minY - pm.y, pm.x - maxX, pm.y - maxY); minX = Math.min(minX, pm.x); @@ -1382,10 +1385,10 @@ class Transform { } }; - processSegment(topLeft.x, topLeft.y, topRight.x, topRight.y); - processSegment(topRight.x, topRight.y, bottomRight.x, bottomRight.y); - processSegment(bottomRight.x, bottomRight.y, bottomLeft.x, bottomLeft.y); - processSegment(bottomLeft.x, bottomLeft.y, topLeft.x, topLeft.y); + processSegment(left, top, right, top); + processSegment(right, top, right, bottom); + processSegment(right, bottom, left, bottom); + processSegment(left, bottom, left, top); const sw = new LngLat(lngFromMercatorX(minX), latFromMercatorY(minY)); const ne = new LngLat(lngFromMercatorX(maxX), latFromMercatorY(maxY)); From 83ca86904722c15cdaca620a4b034c9b09d27901 Mon Sep 17 00:00:00 2001 From: Stepan Kuzmin Date: Mon, 17 Oct 2022 14:09:03 +0300 Subject: [PATCH 09/10] Include poles in bounds if visible --- src/geo/transform.js | 14 +++++++++++--- test/unit/ui/map.test.js | 22 +++++++++++++++++++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/geo/transform.js b/src/geo/transform.js index 4130948eb45..c152fc82046 100644 --- a/src/geo/transform.js +++ b/src/geo/transform.js @@ -17,7 +17,7 @@ import assert from 'assert'; import getProjectionAdjustments, {getProjectionAdjustmentInverted, getScaleAdjustment, getProjectionInterpolationT} from './projection/adjustments.js'; import {getPixelsToTileUnitsMatrix} from '../source/pixels_to_tile_units.js'; import {UnwrappedTileID, OverscaledTileID, CanonicalTileID} from '../source/tile_id.js'; -import {calculateGlobeMatrix, GLOBE_ZOOM_THRESHOLD_MIN, GLOBE_SCALE_MATCH_LATITUDE} from '../geo/projection/globe_util.js'; +import {calculateGlobeMatrix, isLngLatBehindGlobe, GLOBE_ZOOM_THRESHOLD_MIN, GLOBE_SCALE_MATCH_LATITUDE} from '../geo/projection/globe_util.js'; import {projectClamped} from '../symbol/projection.js'; import type Projection from '../geo/projection/projection.js'; @@ -1390,8 +1390,16 @@ class Transform { processSegment(right, bottom, left, bottom); processSegment(left, bottom, left, top); - const sw = new LngLat(lngFromMercatorX(minX), latFromMercatorY(minY)); - const ne = new LngLat(lngFromMercatorX(maxX), latFromMercatorY(maxY)); + // Check if minY is behind the globe and the north pole is visible + const northPoleIsVisible = latFromMercatorY(minY) < 90 && + !isLngLatBehindGlobe(this, new LngLat(this.center.lat, 90)); + + // Check if maxY is behind the globe and the south pole is visible + const southPoleIsVisible = latFromMercatorY(maxY) > -90 && + !isLngLatBehindGlobe(this, new LngLat(this.center.lat, -90)); + + const ne = new LngLat(lngFromMercatorX(maxX), northPoleIsVisible ? 90 : latFromMercatorY(minY)); + const sw = new LngLat(lngFromMercatorX(minX), southPoleIsVisible ? -90 : latFromMercatorY(maxY)); return new LngLatBounds(sw, ne); } diff --git a/test/unit/ui/map.test.js b/test/unit/ui/map.test.js index c220a0307cd..5fa2faf7027 100755 --- a/test/unit/ui/map.test.js +++ b/test/unit/ui/map.test.js @@ -1441,7 +1441,27 @@ test('Map', (t) => { t.same( toFixed(globeBounds.toArray()), - toFixed([[ -73.8873304141, 73.8873304141, ], [ 73.8873304141, -73.8873304141]]) + toFixed([[ -73.8873304141, -73.8873304141, ], [ 73.8873304141, 73.8873304141]]) + ); + + map.setBearing(80); + map.setCenter({lng: 0, lat: 90}); + + const northBounds = map.getBounds(); + t.same(northBounds.getNorth(), 90); + t.same( + toFixed(northBounds.toArray()), + toFixed([[ -169.7072944003, 11.2373406095 ], [ 175.8448619060, 90 ]]) + ); + + map.setBearing(180); + map.setCenter({lng: 0, lat: -90}); + + const southBounds = map.getBounds(); + t.same(southBounds.getSouth(), -90); + t.same( + toFixed(southBounds.toArray()), + toFixed([[ -165.5559158623, -90 ], [ 180, -11.1637985859]]) ); t.end(); From bd6c01d42b53f258231b8630200531af79e9d7d1 Mon Sep 17 00:00:00 2001 From: Stepan Kuzmin Date: Mon, 17 Oct 2022 14:16:01 +0300 Subject: [PATCH 10/10] Drop warning about getBounds API on Globe --- src/ui/map.js | 3 --- test/unit/ui/map.test.js | 1 - 2 files changed, 4 deletions(-) diff --git a/src/ui/map.js b/src/ui/map.js index a738e11e400..2cf5689ba6f 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -798,9 +798,6 @@ class Map extends Camera { * const bounds = map.getBounds(); */ getBounds(): LngLatBounds | null { - if (this.transform.projection.name === 'globe') { - warnOnce('Globe projection does not support getBounds API, this API may behave unexpectedly."'); - } return this.transform.getBounds(); } diff --git a/test/unit/ui/map.test.js b/test/unit/ui/map.test.js index 5fa2faf7027..7bead149806 100755 --- a/test/unit/ui/map.test.js +++ b/test/unit/ui/map.test.js @@ -1436,7 +1436,6 @@ test('Map', (t) => { ); map.setProjection('globe'); - t.stub(console, 'warn'); const globeBounds = map.getBounds(); t.same(