| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | import type { Coordinate } from 'features/controlLayers/store/types'; |
| | import { assert } from 'tsafe'; |
| |
|
| | |
| | function getSqDist(p1: Coordinate, p2: Coordinate) { |
| | const dx = p1.x - p2.x; |
| | const dy = p1.y - p2.y; |
| |
|
| | return dx * dx + dy * dy; |
| | } |
| |
|
| | |
| | function getSqSegDist(p: Coordinate, p1: Coordinate, p2: Coordinate) { |
| | let x = p1.x; |
| | let y = p1.y; |
| | let dx = p2.x - x; |
| | let dy = p2.y - y; |
| |
|
| | if (dx !== 0 || dy !== 0) { |
| | const t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy); |
| |
|
| | if (t > 1) { |
| | x = p2.x; |
| | y = p2.y; |
| | } else if (t > 0) { |
| | x += dx * t; |
| | y += dy * t; |
| | } |
| | } |
| |
|
| | dx = p.x - x; |
| | dy = p.y - y; |
| |
|
| | return dx * dx + dy * dy; |
| | } |
| | |
| |
|
| | |
| | function simplifyRadialDist(points: Coordinate[], sqTolerance: number): Coordinate[] { |
| | let prevPoint = points[0]!; |
| | const newPoints = [prevPoint]; |
| | let point: Coordinate; |
| |
|
| | for (let i = 1, len = points.length; i < len; i++) { |
| | point = points[i]!; |
| |
|
| | if (getSqDist(point, prevPoint!) > sqTolerance) { |
| | newPoints.push(point); |
| | prevPoint = point; |
| | } |
| | } |
| |
|
| | if (prevPoint !== point!) { |
| | newPoints.push(point!); |
| | } |
| |
|
| | return newPoints; |
| | } |
| |
|
| | function simplifyDPStep( |
| | points: Coordinate[], |
| | first: number, |
| | last: number, |
| | sqTolerance: number, |
| | simplified: Coordinate[] |
| | ) { |
| | let maxSqDist = sqTolerance; |
| | let index; |
| |
|
| | for (let i = first + 1; i < last; i++) { |
| | const sqDist = getSqSegDist(points[i]!, points[first]!, points[last]!); |
| |
|
| | if (sqDist > maxSqDist) { |
| | index = i; |
| | maxSqDist = sqDist; |
| | } |
| | } |
| |
|
| | if (maxSqDist > sqTolerance) { |
| | if (index! - first > 1) { |
| | simplifyDPStep(points, first, index!, sqTolerance, simplified); |
| | } |
| | simplified.push(points[index!]!); |
| | if (last - index! > 1) { |
| | simplifyDPStep(points, index!, last, sqTolerance, simplified); |
| | } |
| | } |
| | } |
| |
|
| | |
| | function simplifyDouglasPeucker(points: Coordinate[], sqTolerance: number) { |
| | const last = points.length - 1; |
| |
|
| | const simplified = [points[0]!]; |
| | simplifyDPStep(points, 0, last, sqTolerance, simplified); |
| | simplified.push(points[last]!); |
| |
|
| | return simplified; |
| | } |
| |
|
| | type SimplifyOptions = { |
| | tolerance?: number; |
| | highestQuality?: boolean; |
| | }; |
| |
|
| | |
| | function simplifyCoords(points: Coordinate[], options?: SimplifyOptions): Coordinate[] { |
| | const { tolerance, highestQuality } = { ...options, tolerance: 1, highestQuality: false }; |
| |
|
| | if (points.length <= 2) { |
| | return points; |
| | } |
| |
|
| | const sqTolerance = tolerance * tolerance; |
| |
|
| | const firstPassPoints = highestQuality ? points : simplifyRadialDist(points, sqTolerance); |
| | const secondPassPoints = simplifyDouglasPeucker(firstPassPoints, sqTolerance); |
| |
|
| | return secondPassPoints; |
| | } |
| |
|
| | function coordsToFlatNumbersArray(coords: Coordinate[]): number[] { |
| | return coords.flatMap((coord) => [coord.x, coord.y]); |
| | } |
| |
|
| | function flatNumbersArrayToCoords(array: number[]): Coordinate[] { |
| | assert(array.length % 2 === 0, 'Array length must be even'); |
| | const coords: Coordinate[] = []; |
| | for (let i = 0; i < array.length; i += 2) { |
| | coords.push({ x: array[i]!, y: array[i + 1]! }); |
| | } |
| | return coords; |
| | } |
| |
|
| | export function simplifyFlatNumbersArray(array: number[], options?: SimplifyOptions): number[] { |
| | return coordsToFlatNumbersArray(simplifyCoords(flatNumbersArrayToCoords(array), options)); |
| | } |
| |
|