UNPKG

maplibre-gl

Version:

BSD licensed community fork of mapbox-gl, a WebGL interactive maps library

208 lines (173 loc) 6.74 kB
import {describe, test, expect} from 'vitest'; import Point from '@mapbox/point-geometry'; import {roundPolygonCorners} from './round_polygon_corners.ts'; import {CanonicalTileID} from '../../tile/tile_id.ts'; function round(p: Point):GeoJSON.Position { // add 0 to normalize -0 into 0 const x = Math.round(p.x * 100) / 100 + 0; const y = Math.round(p.y * 100) / 100 + 0; return [x, y]; } const square = [ new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10), new Point(0, 0) ]; describe('roundPolygonCorners', () => { const canonical = new CanonicalTileID(10, 500, 300); test('returns original polygon reference when distance is zero', () => { const input = [[ new Point(0, 0), new Point(100, 0), new Point(100, 100), new Point(0, 100), new Point(0, 0) ]]; const output = roundPolygonCorners(input, 0, canonical); expect(output).toBe(input); }); test('returns original polygon reference when distance is negative', () => { const input = [[ new Point(0, 0), new Point(100, 0), new Point(100, 100), new Point(0, 100), new Point(0, 0) ]]; const output = roundPolygonCorners(input, -1, canonical); expect(output).toBe(input); }); test('returns unchanged ring for degenerate line rings', () => { const input = [[ new Point(0, 0), new Point(50, 50), new Point(0, 0) ]]; const output = roundPolygonCorners(input, 10, canonical); expect(output).toStrictEqual(input); }); test('collapses every arc back onto its corner when the distance is below one tile unit', () => { const output = roundPolygonCorners([square], 0.1, canonical); expect(output[0].map(round)).toEqual(square.map(round)); }); test('rounds corners of a square polygon into arc vertices', () => { const output = roundPolygonCorners([square], 2, canonical); const points = output[0].map(round); expect(points).toEqual([ [0, 1], [0, 0], [1, 0], [9, 0], [10, 0], [10, 1], [10, 9], [10, 10], [9, 10], [1, 10], [0, 10], [0, 9], [0, 1] ]); }); test('clamps corner rounding distance to 20% of edge length when requested distance is large', () => { const output = roundPolygonCorners([square], 1000, canonical); const points = output[0].map(round); expect(points).toEqual([ [0, 2], [0, 1], [1, 0], [2, 0], [8, 0], [9, 0], [10, 1], [10, 2], [10, 8], [10, 9], [9, 10], [8, 10], [2, 10], [1, 10], [0, 9], [0, 8], [0, 2] ]); }); test('emits only integer tile coordinates, so that the triangulator cannot merge arc points itself', () => { const input = [[ new Point(0, 0), new Point(137, 41), new Point(96, 158), new Point(13, 111), new Point(0, 0) ]]; for (const distance of [0.5, 1, 2, 5, 50, 1000]) { for (const ring of roundPolygonCorners(input, distance, canonical)) { for (const point of ring) { expect(point.x).toBe(Math.round(point.x)); expect(point.y).toBe(Math.round(point.y)); } } } }); test('leaves the corners of a cut sharp and rounds the rest of the same ring', () => { const input = [[ new Point(1000, -100), new Point(3000, -100), new Point(3000, 4000), new Point(1000, 4000), new Point(1000, -100) ]]; const output = roundPolygonCorners(input, 1000, canonical); const points = output[0].map(round); expect(points).toEqual([ [1000, -100], [3000, -100], [3000, 3600], [2946, 3800], [2800, 3946], [2600, 4000], [1400, 4000], [1200, 3946], [1054, 3800], [1000, 3600], [1000, -100] ]); }); test('rounds a corner of the feature that sits in the buffer, since only a cut stays sharp', () => { const input = [[ new Point(1200, 200), new Point(2000, -100), new Point(2800, 200), new Point(2000, 3000), new Point(1200, 200) ]]; const output = roundPolygonCorners(input, 1000, canonical); const points = output[0].map(round); expect(points).not.toContainEqual([2000, -100]); expect(output[0].length).toBeGreaterThan(input[0].length); }); test('drops arc points that collapse onto their neighbour', () => { const output = roundPolygonCorners([square], 2, canonical); const ring = output[0]; for (let i = 1; i < ring.length; i++) { expect([ring[i].x, ring[i].y]).not.toEqual([ring[i - 1].x, ring[i - 1].y]); } expect([ring[0].x, ring[0].y]).toEqual([ring[ring.length - 1].x, ring[ring.length - 1].y]); }); test('preserves near-zero-degree spike vertex without adding arc points', () => { const input = [[ new Point(0, 0), new Point(100, 0), new Point(0, 0.4), new Point(0, 0) ]]; const output = roundPolygonCorners(input, 5, canonical); const points = output[0].map(round); expect(points).toEqual([[0, 0], [100, 0], [0, 0.4], [0, 0]]); }); test('preserves collinear 180-degree vertices without adding arc points', () => { const input = [[ new Point(0, 0), new Point(5, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10), new Point(0, 0) ]]; const output = roundPolygonCorners(input, 5, canonical); const points = output[0].map(round); expect(points).toEqual([ [0, 1], [0, 0], [1, 0], [5, 0], [9, 0], [10, 0], [10, 1], [10, 8], [10, 9], [9, 10], [8, 10], [2, 10], [1, 10], [0, 9], [0, 8], [0, 1] ]); }); test('scales arc points with corner sharpness up to the segment maximum', () => { const input = [[ new Point(0, 0), new Point(100, 0), new Point(50, 20), new Point(0, 0) ]]; const output = roundPolygonCorners(input, 5, canonical); const points = output[0].map(round); expect(points).toEqual([ [2, 1], [2, 0], [98, 0], [98, 1], [52, 19], [50, 20], [48, 19], [2, 1] ]); }); });