import * as _ from 'lodash' ; /**
 * General geometric operations.
 */ export interface PolyLineDistance { d2 : number ; dLine : number ; dPerp : number ; } function add ( a , b ) { return [ a [ 0 ] + b [ 0 ] , a [ 1 ] + b [ 1 ] ] ; } function sub ( a , b ) { return [ a [ 0 ] - b [ 0 ] , a [ 1 ] - b [ 1 ] ] ; } function dot ( a , b ) { return a [ 0 ] * b [ 0 ] + a [ 1 ] * b [ 1 ] ; } export function vectorNorm ( v ) { return Math . sqrt ( dot ( v , v ) ) ; } function scale ( v , k ) { return [ v [ 0 ] * k , v [ 1 ] * k ] ; } function distance2 ( a , b ) { const dx = a [ 0 ] - b [ 0 ] ; const dy = a [ 1 ] - b [ 1 ] ; return dx * dx + dy * dy ; } function distance ( a , b ) { return Math . sqrt ( distance2 ( a , b ) ) ; } export function closestPointOnLineSegment ( a , b , pt ) { const bMinusA = sub ( b , a ) ; const bAnorm = vectorNorm ( bMinusA ) ; const ab = scale ( bMinusA , 1 / bAnorm ) ; const [ dx , dy ] = ab ; const ptA = sub ( pt , a ) ; const dLine = dx * ptA [ 0 ] + dy * ptA [ 1 ] ; const dPerp = dx * ptA [ 1 ] - dy * ptA [ 0 ] ; if ( dLine < 0 ) { return { d2 : distance2 ( a , pt ) , dLine , dPerp } ; } else if ( dLine > bAnorm ) { return { d2 : distance2 ( pt , b ) , dLine , dPerp } ; } else { return { d2 : dPerp * dPerp , dLine , dPerp } ; } } /** Find the distance from a point to the closest point on a polyline. */ export function polylineDistance ( vertices , pt , isClosed = false ) { let totalD = 0 ; let closestD = null ; const lastIndex = isClosed ? vertices . length : vertices . length - 1 ; for ( let i = 0 ; i < lastIndex ; i ++ ) { const a = vertices [ i ] ; const b = vertices [ ( i + 1 ) % vertices . length ] ; const thisD = closestPointOnLineSegment ( a , b , pt ) ; if ( ! closestD || thisD . d2 < closestD . d2 ) { closestD = thisD ; closestD . dLine += totalD ; } const len = distance ( a , b ) ; totalD += len ; } if ( ! closestD ) { throw new Error ( 'polylines must have 2+ vertices' ) ; } return closestD ; } /** Return the point at distance d along a polyline. */ export function pointAlongPolyline ( vertices , d , isClosed = false ) { let totalD = 0 ; const lastIndex = isClosed ? vertices . length : vertices . length - 1 ; for ( let i = 0 ; i < lastIndex ; i ++ ) { const a = vertices [ i ] ; const b = vertices [ ( i + 1 ) % vertices . length ] ; const len = distance ( a , b ) ; if ( totalD + len >= d ) { const frac = ( d - totalD ) / len ; return [ a [ 0 ] * ( 1 - frac ) + b [ 0 ] * frac , a [ 1 ] * ( 1 - frac ) + b [ 1 ] * frac ] ; } totalD += len ; } throw new Error ( 'distance is too great for polyline' ) ; } /** Implementation of parallel offset. A positive amount offsets to the right. */ export function offsetLineSegment ( vertices , amount ) { if ( vertices . length !== 2 ) { throw new Error ( 'offsetLineSegment is only implemented for simple line segments.' ) ; } const [ a , b ] = vertices ; const v = sub ( b , a ) ; const vNorm = scale ( v , 1 / vectorNorm ( v ) ) ; const vOffset = scale ( [ vNorm [ 1 ] , - vNorm [ 0 ] ] , amount ) ; return [ add ( a , vOffset ) , add ( b , vOffset ) ] ; } export function findClosestPoint ( point , points ) { return _ . minBy ( points , element => distance ( element , point ) ) ; }