import {describe, test, expect} from 'vitest'; import {LngLat} from './lng_lat'; import {LngLatBounds} from './lng_lat_bounds'; import {tileIdToLngLatBounds} from '../tile/tile_id_to_lng_lat_bounds'; import {CanonicalTileID} from '../tile/tile_id'; import {EXTENT} from '../data/extent'; describe('LngLatBounds', () => { test('constructor', () => { const sw = new LngLat(0, 0); const ne = new LngLat(-10, 10); const bounds = new LngLatBounds(sw, ne); expect(bounds.getSouth()).toBe(0); expect(bounds.getWest()).toBe(0); expect(bounds.getNorth()).toBe(10); expect(bounds.getEast()).toBe(-10); }); test('constructor across dateline', () => { const sw = new LngLat(170, 0); const ne = new LngLat(-170, 10); const bounds = new LngLatBounds(sw, ne); expect(bounds.getSouth()).toBe(0); expect(bounds.getWest()).toBe(170); expect(bounds.getNorth()).toBe(10); expect(bounds.getEast()).toBe(-170); }); test('constructor across pole', () => { const sw = new LngLat(0, 85); const ne = new LngLat(-10, -85); const bounds = new LngLatBounds(sw, ne); expect(bounds.getSouth()).toBe(85); expect(bounds.getWest()).toBe(0); expect(bounds.getNorth()).toBe(-85); expect(bounds.getEast()).toBe(-10); }); test('constructor no args', () => { const bounds = new LngLatBounds(); const t1 = () => { bounds.getCenter(); }; expect(t1).toThrow(); }); test('extend with coordinate', () => { const bounds = new LngLatBounds([0, 0], [10, 10]); bounds.extend([-10, -10]); expect(bounds.getSouth()).toBe(-10); expect(bounds.getWest()).toBe(-10); expect(bounds.getNorth()).toBe(10); expect(bounds.getEast()).toBe(10); bounds.extend(new LngLat(-15, -15)); expect(bounds.getSouth()).toBe(-15); expect(bounds.getWest()).toBe(-15); expect(bounds.getNorth()).toBe(10); expect(bounds.getEast()).toBe(10); bounds.extend([-80, -80, 80, 80]); expect(bounds.getSouth()).toBe(-80); expect(bounds.getWest()).toBe(-80); expect(bounds.getNorth()).toBe(80); expect(bounds.getEast()).toBe(80); bounds.extend({lng: -90, lat: -90}); expect(bounds.getSouth()).toBe(-90); expect(bounds.getWest()).toBe(-90); expect(bounds.getNorth()).toBe(80); expect(bounds.getEast()).toBe(80); bounds.extend({lon: 90, lat: 90}); expect(bounds.getSouth()).toBe(-90); expect(bounds.getWest()).toBe(-90); expect(bounds.getNorth()).toBe(90); expect(bounds.getEast()).toBe(90); }); test('extend with bounds', () => { const bounds1 = new LngLatBounds([0, 0], [10, 10]); const bounds2 = new LngLatBounds([-10, -10], [10, 10]); bounds1.extend(bounds2); expect(bounds1.getSouth()).toBe(-10); expect(bounds1.getWest()).toBe(-10); expect(bounds1.getNorth()).toBe(10); expect(bounds1.getEast()).toBe(10); const bounds4 = new LngLatBounds([-20, -20, 20, 20]); bounds1.extend(bounds4); expect(bounds1.getSouth()).toBe(-20); expect(bounds1.getWest()).toBe(-20); expect(bounds1.getNorth()).toBe(20); expect(bounds1.getEast()).toBe(20); const bounds5 = new LngLatBounds(); bounds1.extend(bounds5); expect(bounds1.getSouth()).toBe(-20); expect(bounds1.getWest()).toBe(-20); expect(bounds1.getNorth()).toBe(20); expect(bounds1.getEast()).toBe(20); }); test('extend with null', () => { const bounds = new LngLatBounds([0, 0], [10, 10]); bounds.extend(null); expect(bounds.getSouth()).toBe(0); expect(bounds.getWest()).toBe(0); expect(bounds.getNorth()).toBe(10); expect(bounds.getEast()).toBe(10); }); test('extend undefined bounding box', () => { const bounds1 = new LngLatBounds(undefined, undefined); const bounds2 = new LngLatBounds([-10, -10], [10, 10]); bounds1.extend(bounds2); expect(bounds1.getSouth()).toBe(-10); expect(bounds1.getWest()).toBe(-10); expect(bounds1.getNorth()).toBe(10); expect(bounds1.getEast()).toBe(10); }); test('extend same LngLat instance', () => { const point = new LngLat(0, 0); const bounds = new LngLatBounds(point, point); bounds.extend(new LngLat(15, 15)); expect(bounds.getSouth()).toBe(0); expect(bounds.getWest()).toBe(0); expect(bounds.getNorth()).toBe(15); expect(bounds.getEast()).toBe(15); }); test('accessors', () => { const sw = new LngLat(0, 0); const ne = new LngLat(-10, -20); const bounds = new LngLatBounds(sw, ne); expect(bounds.getCenter()).toEqual(new LngLat(-5, -10)); expect(bounds.getSouth()).toBe(0); expect(bounds.getWest()).toBe(0); expect(bounds.getNorth()).toBe(-20); expect(bounds.getEast()).toBe(-10); expect(bounds.getSouthWest()).toEqual(new LngLat(0, 0)); expect(bounds.getSouthEast()).toEqual(new LngLat(-10, 0)); expect(bounds.getNorthEast()).toEqual(new LngLat(-10, -20)); expect(bounds.getNorthWest()).toEqual(new LngLat(0, -20)); }); test('convert', () => { const sw = new LngLat(0, 0); const ne = new LngLat(-10, 10); const bounds = new LngLatBounds(sw, ne); expect(LngLatBounds.convert(undefined)).toBeUndefined(); expect(LngLatBounds.convert(bounds)).toEqual(bounds); expect(LngLatBounds.convert([sw, ne])).toEqual(bounds); expect( LngLatBounds.convert([bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()]) ).toEqual(bounds); }); test('toArray', () => { const llb = new LngLatBounds([-73.9876, 40.7661], [-73.9397, 40.8002]); expect(llb.toArray()).toEqual([[-73.9876, 40.7661], [-73.9397, 40.8002]]); }); test('toString', () => { const llb = new LngLatBounds([-73.9876, 40.7661], [-73.9397, 40.8002]); expect(llb.toString()).toBe('LngLatBounds(LngLat(-73.9876, 40.7661), LngLat(-73.9397, 40.8002))'); }); test('isEmpty', () => { const nullBounds = new LngLatBounds(); expect(nullBounds.isEmpty()).toBe(true); const sw = new LngLat(0, 0); const ne = new LngLat(-10, 10); const bounds = new LngLatBounds(sw, ne); expect(bounds.isEmpty()).toBe(false); }); test('fromLngLat', () => { const center0 = new LngLat(0, 0); const center1 = new LngLat(-73.9749, 40.7736); const center0Radius10 = LngLatBounds.fromLngLat(center0, 10); const center1Radius10 = LngLatBounds.fromLngLat(center1, 10); const center1Radius0 = LngLatBounds.fromLngLat(center1); expect(center0Radius10.toArray()).toEqual( [[-0.00008983152770714982, -0.00008983152770714982], [0.00008983152770714982, 0.00008983152770714982]] ); expect(center1Radius10.toArray()).toEqual( [[-73.97501862141328, 40.77351016847229], [-73.97478137858673, 40.77368983152771]] ); expect(center1Radius0.toArray()).toEqual([[-73.9749, 40.7736], [-73.9749, 40.7736]]); }); describe('LngLatBounds adjustAntiMeridian tests', () => { test('kenya', () => { const sw = new LngLat(32.958984, -5.353521); const ne = new LngLat(43.50585, 5.615985); const bounds = new LngLatBounds(sw, ne).adjustAntiMeridian(); expect(bounds.getSouth()).toBe(-5.353521); expect(bounds.getWest()).toBe(32.958984); expect(bounds.getNorth()).toBe(5.615985); expect(bounds.getEast()).toBe(43.50585); }); test('normal cross (crossing antimeridian)', () => { const sw = new LngLat(170, 0); const ne = new LngLat(-170, 10); const bounds = new LngLatBounds(sw, ne).adjustAntiMeridian(); expect(bounds.getSouth()).toBe(0); expect(bounds.getWest()).toBe(170); expect(bounds.getNorth()).toBe(10); expect(bounds.getEast()).toBe(190); }); test('exactly meridian (crossing antimeridian)', () => { const sw = new LngLat(180, -20); const ne = new LngLat(-180, 20); const bounds = new LngLatBounds(sw, ne).adjustAntiMeridian(); expect(bounds.getSouth()).toBe(-20); expect(bounds.getWest()).toBe(180); expect(bounds.getNorth()).toBe(20); expect(bounds.getEast()).toBe(180); }); test('small cross (crossing antimeridian)', () => { const sw = new LngLat(179, -5); const ne = new LngLat(-179, 5); const bounds = new LngLatBounds(sw, ne).adjustAntiMeridian(); expect(bounds.getSouth()).toBe(-5); expect(bounds.getWest()).toBe(179); expect(bounds.getNorth()).toBe(5); expect(bounds.getEast()).toBe(181); }); test('large cross (crossing antimeridian)', () => { const sw = new LngLat(100, -30); const ne = new LngLat(-100, 30); const bounds = new LngLatBounds(sw, ne).adjustAntiMeridian(); expect(bounds.getSouth()).toBe(-30); expect(bounds.getWest()).toBe(100); expect(bounds.getNorth()).toBe(30); expect(bounds.getEast()).toBe(260); }); test('reverse cross (crossing antimeridian)', () => { const sw = new LngLat(-170, 0); const ne = new LngLat(170, 10); const bounds = new LngLatBounds(sw, ne).adjustAntiMeridian(); expect(bounds.getSouth()).toBe(0); expect(bounds.getWest()).toBe(-170); expect(bounds.getNorth()).toBe(10); expect(bounds.getEast()).toBe(170); }); test('reverse not cross', () => { const sw = new LngLat(150, 0); const ne = new LngLat(170, 10); const bounds = new LngLatBounds(sw, ne).adjustAntiMeridian(); expect(bounds.getSouth()).toBe(0); expect(bounds.getWest()).toBe(150); expect(bounds.getNorth()).toBe(10); expect(bounds.getEast()).toBe(170); }); test('same longitude', () => { const sw = new LngLat(175, -10); const ne = new LngLat(175, 10); const bounds = new LngLatBounds(sw, ne).adjustAntiMeridian(); expect(bounds.getSouth()).toBe(-10); expect(bounds.getWest()).toBe(175); expect(bounds.getNorth()).toBe(10); expect(bounds.getEast()).toBe(175); }); test('full world', () => { const sw = new LngLat(-180, -90); const ne = new LngLat(180, 90); const bounds = new LngLatBounds(sw, ne).adjustAntiMeridian(); expect(bounds.getSouth()).toBe(-90); expect(bounds.getWest()).toBe(-180); expect(bounds.getNorth()).toBe(90); expect(bounds.getEast()).toBe(180); }); test('across pole', () => { const sw = new LngLat(0, 85); const ne = new LngLat(-10, -85); const bounds = new LngLatBounds(sw, ne).adjustAntiMeridian(); expect(bounds.getSouth()).toBe(85); expect(bounds.getWest()).toBe(0); expect(bounds.getNorth()).toBe(-85); expect(bounds.getEast()).toBe(350); }); test('across pole reverse', () => { const sw = new LngLat(-10, -85); const ne = new LngLat(0, 85); const bounds = new LngLatBounds(sw, ne).adjustAntiMeridian(); expect(bounds.getSouth()).toBe(-85); expect(bounds.getWest()).toBe(-10); expect(bounds.getNorth()).toBe(85); expect(bounds.getEast()).toBe(0); }); test('across dateline', () => { const sw = new LngLat(170, 0); const ne = new LngLat(-170, 10); const bounds = new LngLatBounds(sw, ne).adjustAntiMeridian(); expect(bounds.getSouth()).toBe(0); expect(bounds.getWest()).toBe(170); expect(bounds.getNorth()).toBe(10); expect(bounds.getEast()).toBe(190); }); }); describe('contains', () => { describe('point', () => { test('point is in bounds', () => { const llb = new LngLatBounds([-1, -1], [1, 1]); const ll = {lng: 0, lat: 0}; expect(llb.contains(ll)).toBeTruthy(); }); test('point is not in bounds', () => { const llb = new LngLatBounds([-1, -1], [1, 1]); const ll = {lng: 3, lat: 3}; expect(llb.contains(ll)).toBeFalsy(); }); test('point is in bounds that spans dateline', () => { const llb = new LngLatBounds([190, -10], [170, 10]); const ll = {lng: 180, lat: 0}; expect(llb.contains(ll)).toBeTruthy(); }); test('point is not in bounds that spans dateline', () => { const llb = new LngLatBounds([190, -10], [170, 10]); const ll = {lng: 0, lat: 0}; expect(llb.contains(ll)).toBeFalsy(); }); }); }); describe('intersects', () => { test('bounds intersect', () => { const bounds1 = new LngLatBounds([0, 0], [10, 10]); const bounds2 = new LngLatBounds([5, 5], [15, 15]); expect(bounds1.intersects(bounds2)).toBe(true); }); test('bounds do not intersect', () => { const bounds1 = new LngLatBounds([0, 0], [10, 10]); const bounds2 = new LngLatBounds([20, 20], [30, 30]); expect(bounds1.intersects(bounds2)).toBe(false); }); describe('dateline crossing', () => { test('both bounds wrap around dateline - always intersect', () => { const bounds1 = new LngLatBounds([170, 0], [-170, 10]); const bounds2 = new LngLatBounds([160, 5], [-160, 15]); expect(bounds1.intersects(bounds2)).toBe(true); }); test('only first bounds wraps - intersects on east side', () => { const bounds1 = new LngLatBounds([170, 0], [-170, 10]); const bounds2 = new LngLatBounds([165, 0], [175, 10]); expect(bounds1.intersects(bounds2)).toBe(true); }); test('only first bounds wraps - intersects on west side', () => { const bounds1 = new LngLatBounds([170, 0], [-170, 10]); const bounds2 = new LngLatBounds([-175, 0], [-165, 10]); expect(bounds1.intersects(bounds2)).toBe(true); }); test('only first bounds wraps - does not intersect (in gap)', () => { const bounds1 = new LngLatBounds([170, 0], [-170, 10]); const bounds2 = new LngLatBounds([0, 0], [10, 10]); expect(bounds1.intersects(bounds2)).toBe(false); }); test('only second bounds wraps - intersects on east side', () => { const bounds1 = new LngLatBounds([165, 0], [175, 10]); const bounds2 = new LngLatBounds([170, 0], [-170, 10]); expect(bounds1.intersects(bounds2)).toBe(true); }); test('only second bounds wraps - intersects on west side', () => { const bounds1 = new LngLatBounds([-175, 0], [-165, 10]); const bounds2 = new LngLatBounds([170, 0], [-170, 10]); expect(bounds1.intersects(bounds2)).toBe(true); }); test('only second bounds wraps - does not intersect (in gap)', () => { const bounds1 = new LngLatBounds([0, 0], [10, 10]); const bounds2 = new LngLatBounds([170, 0], [-170, 10]); expect(bounds1.intersects(bounds2)).toBe(false); }); test('wrapping bounds with no latitude overlap', () => { const bounds1 = new LngLatBounds([170, 0], [-170, 10]); const bounds2 = new LngLatBounds([160, 20], [-160, 30]); expect(bounds1.intersects(bounds2)).toBe(false); }); test('wrapping tile bounds at dateline intersects with negative longitude bounds', () => { const tileBounds = new LngLatBounds([170, 0], [-170, 10]); const bounds = new LngLatBounds([-180, 5], [-175, 10]); expect(tileBounds.intersects(bounds)).toBe(true); }); test('entire worlds tile should return true', () => { const tileBounds = tileIdToLngLatBounds(new CanonicalTileID(0, 0, 0), 2048 / EXTENT); const bounds = new LngLatBounds([[-8.290589217651302, 44.47966524518165], [20.566067150212803, 50.98693819014929]]); expect(tileBounds.intersects(bounds)).toBe(true); }); test('point feature outside bounds does not intersect', () => { const bounds = new LngLatBounds([0, 0], [10, 10]); const point = new LngLatBounds([20, 5], [20, 5]); expect(bounds.intersects(point)).toBe(false); }); }); }); });