diff --git a/src/data/load_geometry.js b/src/data/load_geometry.js index a4b29a6f88d..97794bc5f58 100644 --- a/src/data/load_geometry.js +++ b/src/data/load_geometry.js @@ -4,7 +4,9 @@ import {warnOnce, clamp} from '../util/util.js'; import EXTENT from './extent.js'; import {lngFromMercatorX, latFromMercatorY} from '../geo/mercator_coordinate.js'; +import resample from '../geo/projection/resample.js'; import Point from '@mapbox/point-geometry'; + import type {CanonicalTileID} from '../source/tile_id.js'; import type {TileTransform} from '../geo/projection/tile_transform.js'; @@ -28,12 +30,6 @@ function clampPoint(point: Point) { return point; } -function pointToLineDist(px, py, ax, ay, bx, by) { - const dx = ax - bx; - const dy = ay - by; - return Math.abs((ay - py) * dx - (ax - px) * dy) / Math.hypot(dx, dy); -} - // a subset of VectorTileGeometry type FeatureWithGeometry = { extent: number; @@ -51,64 +47,29 @@ export default function loadGeometry(feature: FeatureWithGeometry, canonical?: C const featureExtent = feature.extent; const scale = EXTENT / featureExtent; const projection = tileTransform ? tileTransform.projection : undefined; - let z2; const isMercator = !projection || projection.name === 'mercator'; - if (canonical && !isMercator) { - z2 = Math.pow(2, canonical.z); - } function reproject(p) { if (isMercator || !canonical || !tileTransform || !projection) { - return new Point(Math.round(p.x * scale), Math.round(p.y * scale)); + return clampPoint(new Point(Math.round(p.x * scale), Math.round(p.y * scale))); } else { + const z2 = 1 << canonical.z; const lng = lngFromMercatorX((canonical.x + p.x / featureExtent) / z2); const lat = latFromMercatorY((canonical.y + p.y / featureExtent) / z2); const {x, y} = projection.project(lng, lat); - return new Point( + return clampPoint(new Point( Math.round((x * tileTransform.scale - tileTransform.x) * EXTENT), Math.round((y * tileTransform.scale - tileTransform.y) * EXTENT) - ); - } - } - - function addResampled(resampled, startMerc, endMerc, startProj, endProj) { - const midMerc = new Point( - (startMerc.x + endMerc.x) / 2, - (startMerc.y + endMerc.y) / 2); - const midProj = reproject(midMerc); - const err = pointToLineDist(midProj.x, midProj.y, startProj.x, startProj.y, endProj.x, endProj.y); - - if (err >= 1) { - // we're very unlikely to hit max call stack exceeded here, - // but we might want to safeguard against it in the future - addResampled(resampled, startMerc, midMerc, startProj, midProj); - addResampled(resampled, midMerc, endMerc, midProj, endProj); - } else { - resampled.push(clampPoint(endProj)); + )); } } const geometry = feature.loadGeometry(); - for (let r = 0; r < geometry.length; r++) { - const ring = geometry[r]; - const resampled = []; - - for (let i = 0, prevMerc, prevProj; i < ring.length; i++) { - const pointMerc = ring[i]; - const pointProj = reproject(ring[i]); - - if (projection && projection.name !== 'mercator' && prevMerc && prevProj && feature.type !== 1) { - addResampled(resampled, prevMerc, pointMerc, prevProj, pointProj); - } else { - resampled.push(clampPoint(pointProj)); - } - - prevMerc = pointMerc; - prevProj = pointProj; - } - - geometry[r] = resampled; + for (let i = 0; i < geometry.length; i++) { + geometry[i] = !isMercator && feature.type !== 1 ? + resample(geometry[i], reproject, 1) : + geometry[i].map(reproject); } return geometry; diff --git a/src/geo/projection/adjustments.js b/src/geo/projection/adjustments.js index 41b1d52af45..fe4c2720b1f 100644 --- a/src/geo/projection/adjustments.js +++ b/src/geo/projection/adjustments.js @@ -1,8 +1,9 @@ // @flow import LngLat from '../lng_lat.js'; -import MercatorCoordinate from '../mercator_coordinate.js'; +import MercatorCoordinate, {MAX_MERCATOR_LATITUDE} from '../mercator_coordinate.js'; import {mat4, mat2} from 'gl-matrix'; +import {clamp} from '../../util/util.js'; import type {Projection} from './index.js'; import type Transform from '../transform.js'; @@ -37,20 +38,27 @@ function getInterpolationT(transform: Transform) { const rangeAdjustment = Math.log(size / 1024) / Math.LN2; const zoomA = range[0] + rangeAdjustment; const zoomB = range[1] + rangeAdjustment; - const t = Math.max(0, Math.min(1, (transform.zoom - zoomA) / (zoomB - zoomA))); - + const t = clamp((transform.zoom - zoomA) / (zoomB - zoomA), 0, 1); return t; } +// approx. kilometers per longitude degree at equator +const offset = 1 / 40000; + /* * Calculates the scale difference between Mercator and the given projection at a certain location. */ function getZoomAdjustment(projection: Projection, loc: LngLat) { - const loc2 = new LngLat(loc.lng + 360 / 40000, loc.lat); - const p1 = projection.project(loc.lng, loc.lat); - const p2 = projection.project(loc2.lng, loc2.lat); + // make sure we operate within mercator space for adjustments (they can go over for other projections) + const lat = clamp(loc.lat, -MAX_MERCATOR_LATITUDE, MAX_MERCATOR_LATITUDE); + + const loc1 = new LngLat(loc.lng - 180 * offset, lat); + const loc2 = new LngLat(loc.lng + 180 * offset, lat); + + const p1 = projection.project(loc1.lng, lat); + const p2 = projection.project(loc2.lng, lat); - const m1 = MercatorCoordinate.fromLngLat(loc); + const m1 = MercatorCoordinate.fromLngLat(loc1); const m2 = MercatorCoordinate.fromLngLat(loc2); const pdx = p2.x - p1.x; @@ -65,41 +73,47 @@ function getZoomAdjustment(projection: Projection, loc: LngLat) { function getShearAdjustment(projection, zoom, loc, interpT, withoutRotation?: boolean) { - // create a location a tiny amount (~1km) east of the given location - const loc2 = new LngLat(loc.lng + 360 / 40000, loc.lat); + // create two locations a tiny amount (~1km) east and west of the given location + const locw = new LngLat(loc.lng - 180 * offset, loc.lat); + const loce = new LngLat(loc.lng + 180 * offset, loc.lat); - const p1 = projection.project(loc.lng, loc.lat); - const p2 = projection.project(loc2.lng, loc2.lat); + const pw = projection.project(locw.lng, locw.lat); + const pe = projection.project(loce.lng, loce.lat); - const pdx = p2.x - p1.x; - const pdy = p2.y - p1.y; + const pdx = pe.x - pw.x; + const pdy = pe.y - pw.y; // Calculate how much the map would need to be rotated to make east-west in // projected coordinates be left-right const angleAdjust = -Math.atan(pdy / pdx) / Math.PI * 180; - // Find the projected coordinates of two locations, one slightly north and one slightly east. + // Pick a location identical to the original one except for poles to make sure we're within mercator bounds + const mc2 = MercatorCoordinate.fromLngLat(loc); + mc2.y = clamp(mc2.y, -1 + offset, 1 - offset); + const loc2 = mc2.toLngLat(); + const p2 = projection.project(loc2.lng, loc2.lat); + + // Find the projected coordinates of two locations, one slightly south and one slightly east. // Then calculate the transform that would make the projected coordinates of the two locations be: // - equal distances from the original location // - perpendicular to one another // // Only the position of the coordinate to the north is adjusted. // The coordinate to the east stays where it is. - const mc3 = MercatorCoordinate.fromLngLat(loc); - const offset = 1 / 40000; + const mc3 = MercatorCoordinate.fromLngLat(loc2); mc3.x += offset; const loc3 = mc3.toLngLat(); const p3 = projection.project(loc3.lng, loc3.lat); - const pdx3 = p3.x - p1.x; - const pdy3 = p3.y - p1.y; + const pdx3 = p3.x - p2.x; + const pdy3 = p3.y - p2.y; const delta3 = rotate(pdx3, pdy3, angleAdjust); - const mc4 = MercatorCoordinate.fromLngLat(loc); + const mc4 = MercatorCoordinate.fromLngLat(loc2); mc4.y += offset; const loc4 = mc4.toLngLat(); const p4 = projection.project(loc4.lng, loc4.lat); - const pdx4 = p4.x - p1.x; - const pdy4 = p4.y - p1.y; + const pdx4 = p4.x - p2.x; + const pdy4 = p4.y - p2.y; const delta4 = rotate(pdx4, pdy4, angleAdjust); const scale = Math.abs(delta3.x) / Math.abs(delta4.y); diff --git a/src/geo/projection/resample.js b/src/geo/projection/resample.js new file mode 100644 index 00000000000..74389807236 --- /dev/null +++ b/src/geo/projection/resample.js @@ -0,0 +1,49 @@ +// @flow + +import Point from '@mapbox/point-geometry'; + +function pointToLineDist(px, py, ax, ay, bx, by) { + const dx = ax - bx; + const dy = ay - by; + return Math.abs((ay - py) * dx - (ax - px) * dy) / Math.hypot(dx, dy); +} + +function addResampled(resampled, startMerc, endMerc, startProj, endProj, reproject, tolerance) { + const midMerc = new Point( + (startMerc.x + endMerc.x) / 2, + (startMerc.y + endMerc.y) / 2); + + const midProj = reproject(midMerc); + const err = pointToLineDist(midProj.x, midProj.y, startProj.x, startProj.y, endProj.x, endProj.y); + + // if reprojected midPoint is too far from geometric midpoint, recurse into two halves + if (err >= tolerance) { + // we're very unlikely to hit max call stack exceeded here, + // but we might want to safeguard against it in the future + addResampled(resampled, startMerc, midMerc, startProj, midProj, reproject, tolerance); + addResampled(resampled, midMerc, endMerc, midProj, endProj, reproject, tolerance); + + } else { // otherwise, just add the point + resampled.push(endProj); + } +} + +export default function resample(line: Array, reproject: (Point) => Point, tolerance: number): Array { + const resampled = []; + let prevMerc, prevProj; + + for (const pointMerc of line) { + const pointProj = reproject(pointMerc); + + if (prevMerc && prevProj) { + addResampled(resampled, prevMerc, pointMerc, prevProj, pointProj, reproject, tolerance); + } else { + resampled.push(pointProj); + } + + prevMerc = pointMerc; + prevProj = pointProj; + } + + return resampled; +} diff --git a/src/geo/projection/tile_transform.js b/src/geo/projection/tile_transform.js index 506f5356a08..40cb3e01147 100644 --- a/src/geo/projection/tile_transform.js +++ b/src/geo/projection/tile_transform.js @@ -1,6 +1,5 @@ // @flow import {lngFromMercatorX, latFromMercatorY} from '../mercator_coordinate.js'; -import {number as interpolate} from '../../style-spec/util/interpolate.js'; import type {Projection} from './index.js'; export type TileTransform = { @@ -14,37 +13,66 @@ export type TileTransform = { export default function tileTransform(id: Object, projection: Projection) { const s = Math.pow(2, -id.z); + const x1 = (id.x) * s; const x2 = (id.x + 1) * s; const y1 = (id.y) * s; const y2 = (id.y + 1) * s; + + if (projection.name === 'mercator') { + return {scale: 1 << id.z, x: 0, y: 0, x2: 1, y2: 1, projection}; + } + const lng1 = lngFromMercatorX(x1); const lng2 = lngFromMercatorX(x2); const lat1 = latFromMercatorY(y1); const lat2 = latFromMercatorY(y2); - let minX = Infinity; - let minY = Infinity; - let maxX = -Infinity; - let maxY = -Infinity; - - const n = 2; - for (let i = 0; i <= n; i++) { - const lng = lngFromMercatorX(interpolate(x1, x2, i / n)); - const lat = latFromMercatorY(interpolate(y1, y2, i / n)); - - const p0 = projection.project(lng, lat1); - const p1 = projection.project(lng, lat2); - const p2 = projection.project(lng1, lat); - const p3 = projection.project(lng2, lat); - - minX = Math.min(minX, p0.x, p1.x, p2.x, p3.x); - maxX = Math.max(maxX, p0.x, p1.x, p2.x, p3.x); - minY = Math.min(minY, p0.y, p1.y, p2.y, p3.y); - maxY = Math.max(maxY, p0.y, p1.y, p2.y, p3.y); + + 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 max = Math.max(maxX - minX, maxY - minY); const scale = 1 / max; + return { scale, x: minX * scale, diff --git a/src/geo/transform.js b/src/geo/transform.js index 95b09fa5e7a..8694169e1d3 100644 --- a/src/geo/transform.js +++ b/src/geo/transform.js @@ -37,8 +37,7 @@ type ElevationReference = "sea" | "ground"; class Transform { tileSize: number; tileZoom: number; - lngRange: ?[number, number]; - latRange: ?[number, number]; + maxBounds: ?LngLatBounds; // 2^zoom (worldSize = tileSize * scale) scale: number; @@ -93,6 +92,11 @@ class Transform { inverseAdjustmentMatrix: Array; + worldMinX: number; + worldMaxX: number; + worldMinY: number; + worldMaxY: number; + freezeTileCoverage: boolean; cameraElevationReference: ElevationReference; fogCullDistSq: ?number; @@ -131,12 +135,12 @@ class Transform { this._minPitch = (minPitch === undefined || minPitch === null) ? 0 : minPitch; this._maxPitch = (maxPitch === undefined || maxPitch === null) ? 60 : maxPitch; - this.setMaxBounds(); - if (!projection) projection = 'mercator'; this.projectionOptions = projection; this.projection = getProjection(projection); + this.setMaxBounds(); + this.width = 0; this.height = 0; this._center = new LngLat(0, 0); @@ -163,7 +167,7 @@ class Transform { clone._elevation = this._elevation; clone._centerAltitude = this._centerAltitude; clone.tileSize = this.tileSize; - clone.latRange = this.latRange; + clone.setMaxBounds(this.getMaxBounds()); clone.width = this.width; clone.height = this.height; clone.cameraElevationReference = this.cameraElevationReference; @@ -703,10 +707,6 @@ class Transform { if (isNaN(tx) || isNaN(tx2) || isNaN(ty) || isNaN(ty2)) { assert(false); } - //assert(!isNaN(tx)); - //assert(!isNaN(tx2)); - //assert(!isNaN(ty)); - //assert(!isNaN(ty2)); const ret = new Aabb( [(wrap + tx) * numTiles, numTiles * ty, min], [(wrap + tx2) * numTiles, numTiles * ty2, max]); @@ -716,11 +716,13 @@ class Transform { const newRootTile = (wrap: number): any => { const max = maxRange; const min = minRange; + const aabb = this.projection.name === 'mercator' ? + new Aabb([wrap * numTiles, 0, min], [(wrap + 1) * numTiles, numTiles, max]) : + aabbForTile(0, 0, 0, wrap, min, max); return { // With elevation, this._elevation provides z coordinate values. For 2D: // All tiles are on zero elevation plane => z difference is zero - aabb1: new Aabb([wrap * numTiles, 0, min], [(wrap + 1) * numTiles, numTiles, max]), - aabb: aabbForTile(0, 0, 0, wrap, min, max), + aabb, zoom: 0, x: 0, y: 0, @@ -1246,26 +1248,35 @@ class Transform { * Returns the maximum geographical bounds the map is constrained to, or `null` if none set. * @returns {LngLatBounds} {@link LngLatBounds} */ - getMaxBounds(): LngLatBounds | null { - if (!this.latRange || this.latRange.length !== 2 || - !this.lngRange || this.lngRange.length !== 2) return null; - - return new LngLatBounds([this.lngRange[0], this.latRange[0]], [this.lngRange[1], this.latRange[1]]); + getMaxBounds(): ?LngLatBounds { + return this.maxBounds; } /** * Sets or clears the map's geographical constraints. * @param {LngLatBounds} bounds A {@link LngLatBounds} object describing the new geographic boundaries of the map. */ - setMaxBounds(bounds?: LngLatBounds) { + setMaxBounds(bounds: ?LngLatBounds) { + this.maxBounds = bounds; + + let minLat = -MAX_MERCATOR_LATITUDE; + let maxLat = MAX_MERCATOR_LATITUDE; + let minLng = -180; + let maxLng = 180; + if (bounds) { - this.lngRange = [bounds.getWest(), bounds.getEast()]; - this.latRange = [bounds.getSouth(), bounds.getNorth()]; - this._constrain(); - } else { - this.lngRange = null; - this.latRange = [-MAX_MERCATOR_LATITUDE, MAX_MERCATOR_LATITUDE]; + minLat = bounds.getSouth(); + maxLat = bounds.getNorth(); + minLng = bounds.getWest(); + maxLng = bounds.getEast(); } + + this.worldMinX = mercatorXfromLng(minLng) * this.tileSize; + this.worldMaxX = mercatorXfromLng(maxLng) * this.tileSize; + this.worldMinY = mercatorYfromLat(maxLat) * this.tileSize; + this.worldMaxY = mercatorYfromLat(minLat) * this.tileSize; + + this._constrain(); } calculatePosMatrix(unwrappedTileID: UnwrappedTileID, worldSize: number): Float32Array { @@ -1427,101 +1438,69 @@ class Transform { _constrain() { if (!this.center || !this.width || !this.height || this._constraining) return; - this._constraining = true; - - let minY = -90; - let maxY = 90; - let minX = -180; - let maxX = 180; - let sy, sx, x2, y2; - const size = this.size, - unmodified = this._unmodified; - - if (this.latRange) { - const latRange = this.latRange; - minY = mercatorYfromLat(latRange[1]) * this.worldSize; - maxY = mercatorYfromLat(latRange[0]) * this.worldSize; - sy = maxY - minY < size.y ? size.y / (maxY - minY) : 0; - } + // temporarily disable constraining for non-Mercator projections + if (this.projection.name !== 'mercator') return; - if (this.lngRange) { - const lngRange = this.lngRange; - minX = mercatorXfromLng(lngRange[0]) * this.worldSize; - maxX = mercatorXfromLng(lngRange[1]) * this.worldSize; - sx = maxX - minX < size.x ? size.x / (maxX - minX) : 0; - } - - const point = this.point; - - // how much the map should scale to fit the screen into given latitude/longitude ranges - const s = Math.max(sx || 0, sy || 0); - - if (s) { - this.center = this.unproject(new Point( - sx ? (maxX + minX) / 2 : point.x, - sy ? (maxY + minY) / 2 : point.y)); - this.zoom += this.scaleZoom(s); - this._unmodified = unmodified; - this._constraining = false; - return; - } - - if (this.latRange) { - const y = point.y, - h2 = size.y / 2; + this._constraining = true; - if (y - h2 < minY) y2 = minY + h2; - if (y + h2 > maxY) y2 = maxY - h2; + const unmodified = this._unmodified; + const {x, y} = this.point; + let s = 0; + let x2 = x; + let y2 = y; + const w2 = this.width / 2; + const h2 = this.height / 2; + + const minY = this.worldMinY * this.scale; + const maxY = this.worldMaxY * this.scale; + if (y - h2 < minY) y2 = minY + h2; + if (y + h2 > maxY) y2 = maxY - h2; + if (maxY - minY < this.height) { + s = Math.max(s, this.height / (maxY - minY)); + y2 = (maxY + minY) / 2; } - if (this.lngRange) { - const x = point.x, - w2 = size.x / 2; + if (this.maxBounds) { + const minX = this.worldMinX * this.scale; + const maxX = this.worldMaxX * this.scale; if (x - w2 < minX) x2 = minX + w2; if (x + w2 > maxX) x2 = maxX - w2; + if (maxX - minX < this.width) { + s = Math.max(s, this.width / (maxX - minX)); + x2 = (maxX + minX) / 2; + } } - // pan the map if the screen goes off the range - if (x2 !== undefined || y2 !== undefined) { - this.center = this.unproject(new Point( - x2 !== undefined ? x2 : point.x, - y2 !== undefined ? y2 : point.y)); + if (x2 !== x || y2 !== y) { // pan the map to fit the range + this.center = this.unproject(new Point(x2, y2)); + } + if (s) { // scale the map to fit the range + this.zoom += this.scaleZoom(s); } this._constrainCameraAltitude(); - this._unmodified = unmodified; this._constraining = false; } /** - * Returns the minimum zoom at which `this.width` can fit `this.lngRange` - * and `this.height` can fit `this.latRange`. + * Returns the minimum zoom at which `this.width` can fit max longitude range + * and `this.height` can fit max latitude range. * * @returns {number} The zoom value. */ _minZoomForBounds(): number { - const minZoomForDim = (dim: number, range: [number, number]): number => { - return Math.log2(dim / (this.tileSize * Math.abs(range[1] - range[0]))); - }; - let minLatZoom = DEFAULT_MIN_ZOOM; - if (this.latRange) { - const latRange = this.latRange; - minLatZoom = minZoomForDim(this.height, [mercatorYfromLat(latRange[0]), mercatorYfromLat(latRange[1])]); + let minZoom = Math.max(0, this.scaleZoom(this.height / (this.worldMaxY - this.worldMinY))); + if (this.maxBounds) { + minZoom = Math.max(minZoom, this.scaleZoom(this.width / (this.worldMaxX - this.worldMinX))); } - let minLngZoom = DEFAULT_MIN_ZOOM; - if (this.lngRange) { - const lngRange = this.lngRange; - minLngZoom = minZoomForDim(this.width, [mercatorXfromLng(lngRange[0]), mercatorXfromLng(lngRange[1])]); - } - - return Math.max(minLatZoom, minLngZoom); + return minZoom; } /** * Returns the maximum distance of the camera from the center of the bounds, such that - * `this.width` can fit `this.lngRange` and `this.height` can fit `this.latRange`. + * `this.width` can fit max longitude range and `this.height` can fit max latitude range. * In mercator units. * * @returns {number} The mercator z coordinate. @@ -1717,7 +1696,7 @@ class Transform { /** * Apply a 3d translation to the camera position, but clamping it so that - * it respects the bounds set by `this.latRange` and `this.lngRange`. + * it respects the maximum longitude and latitude range set. * * @param {vec3} translation The translation vector. */ diff --git a/src/ui/camera.js b/src/ui/camera.js index 7087194e2ab..97765efde2f 100644 --- a/src/ui/camera.js +++ b/src/ui/camera.js @@ -1492,7 +1492,7 @@ class Camera extends Evented { // interpolating between the two endpoints will cross it. _normalizeCenter(center: LngLat) { const tr = this.transform; - if (!tr.renderWorldCopies || tr.lngRange) return; + if (!tr.renderWorldCopies || tr.maxBounds) return; const delta = center.lng - tr.center.lng; center.lng += diff --git a/src/ui/map.js b/src/ui/map.js index 842c8253866..8c276f165c1 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -682,7 +682,7 @@ class Map extends Camera { * var maxBounds = map.getMaxBounds(); */ getMaxBounds(): LngLatBounds | null { - return this.transform.getMaxBounds(); + return this.transform.getMaxBounds() || null; } /** diff --git a/test/unit/geo/transform.test.js b/test/unit/geo/transform.test.js index d976a116563..f8c81d30a3b 100644 --- a/test/unit/geo/transform.test.js +++ b/test/unit/geo/transform.test.js @@ -94,15 +94,12 @@ test('transform', (t) => { t.end(); }); - t.test('lngRange & latRange constrain zoom and center', (t) => { + t.test('maxBounds constrain zoom and center', (t) => { const transform = new Transform(); transform.center = new LngLat(0, 0); transform.zoom = 10; transform.resize(500, 500); - - transform.lngRange = [-5, 5]; - transform.latRange = [-5, 5]; - + transform.setMaxBounds(LngLatBounds.convert([-5, -5, 5, 5])); transform.zoom = 0; t.equal(transform.zoom, 5.135709286104402); @@ -116,8 +113,8 @@ test('transform', (t) => { t.end(); }); - t.test('_minZoomForBounds respects latRange and lngRange', (t) => { - t.test('it returns 0 when latRange and lngRange are undefined', (t) => { + t.test('_minZoomForBounds respects maxBounds', (t) => { + t.test('it returns 0 when lngRange is undefined', (t) => { const transform = new Transform(); transform.center = new LngLat(0, 0); transform.zoom = 10; @@ -132,8 +129,7 @@ test('transform', (t) => { transform.center = new LngLat(0, 0); transform.zoom = 10; transform.resize(500, 500); - transform.lngRange = [-5, 5]; - transform.latRange = [-5, 5]; + transform.setMaxBounds(LngLatBounds.convert([-5, -5, 5, 5])); const preComputedMinZoom = transform._minZoomForBounds(); transform.zoom = 0; @@ -1323,7 +1319,7 @@ test('transform', (t) => { }); t.test('_translateCameraConstrained', (t) => { - t.test('it clamps at zoom 0 when lngRange and latRange are not defined', (t) => { + t.test('it clamps at zoom 0 when maxBounds are not defined', (t) => { const transform = new Transform(); transform.center = new LngLat(0, 0); transform.zoom = 10; @@ -1358,8 +1354,7 @@ test('transform', (t) => { transform.center = new LngLat(0, 0); transform.zoom = 20; transform.resize(500, 500); - transform.lngRange = [-5, 5]; - transform.latRange = [-5, 5]; + transform.setMaxBounds(LngLatBounds.convert([-5, -5, 5, 5])); //record constrained zoom transform.zoom = 0; diff --git a/test/unit/ui/map.test.js b/test/unit/ui/map.test.js index 2105bca96ae..40e533cd7d2 100755 --- a/test/unit/ui/map.test.js +++ b/test/unit/ui/map.test.js @@ -4,6 +4,7 @@ import window from '../../../src/util/window.js'; import Map from '../../../src/ui/map.js'; import {createMap} from '../../util/index.js'; import LngLat from '../../../src/geo/lng_lat.js'; +import LngLatBounds from '../../../src/geo/lng_lat_bounds.js'; import Tile from '../../../src/source/tile.js'; import {OverscaledTileID} from '../../../src/source/tile_id.js'; import {Event, ErrorEvent} from '../../../src/util/evented.js'; @@ -232,10 +233,10 @@ test('Map', (t) => { t.stub(Map.prototype, '_detectMissingCSS'); t.stub(Map.prototype, '_authenticate'); const map = new Map({container: window.document.createElement('div'), testMode: true}); - map.transform.lngRange = [-120, 140]; - map.transform.latRange = [-60, 80]; + + map.transform.setMaxBounds(LngLatBounds.convert([-120, -60, 140, 80])); map.transform.resize(600, 400); - t.equal(map.transform.zoom, 0.6983039737971012, 'map transform is constrained'); + t.ok(map.transform.zoom, 0.698303973797101, 'map transform is constrained'); t.ok(map.transform.unmodified, 'map transform is not modified'); map.setStyle(createStyle()); map.on('style.load', () => {