1 | import { isBigNumber } from '../../utils/is'
|
2 | import { factory } from '../../utils/factory'
|
3 |
|
4 | const name = 'distance'
|
5 | const dependencies = [
|
6 | 'typed',
|
7 | 'addScalar',
|
8 | 'subtract',
|
9 | 'divideScalar',
|
10 | 'multiplyScalar',
|
11 | 'unaryMinus',
|
12 | 'sqrt',
|
13 | 'abs'
|
14 | ]
|
15 |
|
16 | export const createDistance = factory(name, dependencies, ({ typed, addScalar, subtract, multiplyScalar, divideScalar, unaryMinus, sqrt, abs }) => {
|
17 | |
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 | return typed(name, {
|
71 | 'Array, Array, Array': function (x, y, z) {
|
72 |
|
73 | if (x.length === 2 && y.length === 2 && z.length === 2) {
|
74 | if (!_2d(x)) { throw new TypeError('Array with 2 numbers or BigNumbers expected for first argument') }
|
75 | if (!_2d(y)) { throw new TypeError('Array with 2 numbers or BigNumbers expected for second argument') }
|
76 | if (!_2d(z)) { throw new TypeError('Array with 2 numbers or BigNumbers expected for third argument') }
|
77 | const m = divideScalar(subtract(z[1], z[0]), subtract(y[1], y[0]))
|
78 | const xCoeff = multiplyScalar(multiplyScalar(m, m), y[0])
|
79 | const yCoeff = unaryMinus(multiplyScalar(m, y[0]))
|
80 | const constant = x[1]
|
81 |
|
82 | return _distancePointLine2D(x[0], x[1], xCoeff, yCoeff, constant)
|
83 | } else {
|
84 | throw new TypeError('Invalid Arguments: Try again')
|
85 | }
|
86 | },
|
87 | 'Object, Object, Object': function (x, y, z) {
|
88 | if (Object.keys(x).length === 2 && Object.keys(y).length === 2 && Object.keys(z).length === 2) {
|
89 | if (!_2d(x)) { throw new TypeError('Values of pointX and pointY should be numbers or BigNumbers') }
|
90 | if (!_2d(y)) { throw new TypeError('Values of lineOnePtX and lineOnePtY should be numbers or BigNumbers') }
|
91 | if (!_2d(z)) { throw new TypeError('Values of lineTwoPtX and lineTwoPtY should be numbers or BigNumbers') }
|
92 | if ('pointX' in x && 'pointY' in x && 'lineOnePtX' in y &&
|
93 | 'lineOnePtY' in y && 'lineTwoPtX' in z && 'lineTwoPtY' in z) {
|
94 | const m = divideScalar(subtract(z.lineTwoPtY, z.lineTwoPtX), subtract(y.lineOnePtY, y.lineOnePtX))
|
95 | const xCoeff = multiplyScalar(multiplyScalar(m, m), y.lineOnePtX)
|
96 | const yCoeff = unaryMinus(multiplyScalar(m, y.lineOnePtX))
|
97 | const constant = x.pointX
|
98 |
|
99 | return _distancePointLine2D(x.pointX, x.pointY, xCoeff, yCoeff, constant)
|
100 | } else {
|
101 | throw new TypeError('Key names do not match')
|
102 | }
|
103 | } else {
|
104 | throw new TypeError('Invalid Arguments: Try again')
|
105 | }
|
106 | },
|
107 | 'Array, Array': function (x, y) {
|
108 |
|
109 | if (x.length === 2 && y.length === 3) {
|
110 | if (!_2d(x)) {
|
111 | throw new TypeError('Array with 2 numbers or BigNumbers expected for first argument')
|
112 | }
|
113 | if (!_3d(y)) {
|
114 | throw new TypeError('Array with 3 numbers or BigNumbers expected for second argument')
|
115 | }
|
116 |
|
117 | return _distancePointLine2D(x[0], x[1], y[0], y[1], y[2])
|
118 | } else if (x.length === 3 && y.length === 6) {
|
119 |
|
120 | if (!_3d(x)) {
|
121 | throw new TypeError('Array with 3 numbers or BigNumbers expected for first argument')
|
122 | }
|
123 | if (!_parametricLine(y)) {
|
124 | throw new TypeError('Array with 6 numbers or BigNumbers expected for second argument')
|
125 | }
|
126 |
|
127 | return _distancePointLine3D(x[0], x[1], x[2], y[0], y[1], y[2], y[3], y[4], y[5])
|
128 | } else if (x.length === y.length && x.length > 0) {
|
129 |
|
130 | if (!_containsOnlyNumbers(x)) {
|
131 | throw new TypeError('All values of an array should be numbers or BigNumbers')
|
132 | }
|
133 | if (!_containsOnlyNumbers(y)) {
|
134 | throw new TypeError('All values of an array should be numbers or BigNumbers')
|
135 | }
|
136 |
|
137 | return _euclideanDistance(x, y)
|
138 | } else {
|
139 | throw new TypeError('Invalid Arguments: Try again')
|
140 | }
|
141 | },
|
142 | 'Object, Object': function (x, y) {
|
143 | if (Object.keys(x).length === 2 && Object.keys(y).length === 3) {
|
144 | if (!_2d(x)) {
|
145 | throw new TypeError('Values of pointX and pointY should be numbers or BigNumbers')
|
146 | }
|
147 | if (!_3d(y)) {
|
148 | throw new TypeError('Values of xCoeffLine, yCoeffLine and constant should be numbers or BigNumbers')
|
149 | }
|
150 | if ('pointX' in x && 'pointY' in x && 'xCoeffLine' in y && 'yCoeffLine' in y && 'constant' in y) {
|
151 | return _distancePointLine2D(x.pointX, x.pointY, y.xCoeffLine, y.yCoeffLine, y.constant)
|
152 | } else {
|
153 | throw new TypeError('Key names do not match')
|
154 | }
|
155 | } else if (Object.keys(x).length === 3 && Object.keys(y).length === 6) {
|
156 |
|
157 | if (!_3d(x)) {
|
158 | throw new TypeError('Values of pointX, pointY and pointZ should be numbers or BigNumbers')
|
159 | }
|
160 | if (!_parametricLine(y)) {
|
161 | throw new TypeError('Values of x0, y0, z0, a, b and c should be numbers or BigNumbers')
|
162 | }
|
163 | if ('pointX' in x && 'pointY' in x && 'x0' in y && 'y0' in y && 'z0' in y && 'a' in y && 'b' in y && 'c' in y) {
|
164 | return _distancePointLine3D(x.pointX, x.pointY, x.pointZ, y.x0, y.y0, y.z0, y.a, y.b, y.c)
|
165 | } else {
|
166 | throw new TypeError('Key names do not match')
|
167 | }
|
168 | } else if (Object.keys(x).length === 2 && Object.keys(y).length === 2) {
|
169 |
|
170 | if (!_2d(x)) {
|
171 | throw new TypeError('Values of pointOneX and pointOneY should be numbers or BigNumbers')
|
172 | }
|
173 | if (!_2d(y)) {
|
174 | throw new TypeError('Values of pointTwoX and pointTwoY should be numbers or BigNumbers')
|
175 | }
|
176 | if ('pointOneX' in x && 'pointOneY' in x && 'pointTwoX' in y && 'pointTwoY' in y) {
|
177 | return _euclideanDistance([x.pointOneX, x.pointOneY], [y.pointTwoX, y.pointTwoY])
|
178 | } else {
|
179 | throw new TypeError('Key names do not match')
|
180 | }
|
181 | } else if (Object.keys(x).length === 3 && Object.keys(y).length === 3) {
|
182 |
|
183 | if (!_3d(x)) {
|
184 | throw new TypeError('Values of pointOneX, pointOneY and pointOneZ should be numbers or BigNumbers')
|
185 | }
|
186 | if (!_3d(y)) {
|
187 | throw new TypeError('Values of pointTwoX, pointTwoY and pointTwoZ should be numbers or BigNumbers')
|
188 | }
|
189 | if ('pointOneX' in x && 'pointOneY' in x && 'pointOneZ' in x &&
|
190 | 'pointTwoX' in y && 'pointTwoY' in y && 'pointTwoZ' in y
|
191 | ) {
|
192 | return _euclideanDistance([x.pointOneX, x.pointOneY, x.pointOneZ], [y.pointTwoX, y.pointTwoY, y.pointTwoZ])
|
193 | } else {
|
194 | throw new TypeError('Key names do not match')
|
195 | }
|
196 | } else {
|
197 | throw new TypeError('Invalid Arguments: Try again')
|
198 | }
|
199 | },
|
200 | Array: function (arr) {
|
201 | if (!_pairwise(arr)) { throw new TypeError('Incorrect array format entered for pairwise distance calculation') }
|
202 |
|
203 | return _distancePairwise(arr)
|
204 | }
|
205 | })
|
206 |
|
207 | function _isNumber (a) {
|
208 |
|
209 | return (typeof a === 'number' || isBigNumber(a))
|
210 | }
|
211 |
|
212 | function _2d (a) {
|
213 |
|
214 | if (a.constructor !== Array) {
|
215 | a = _objectToArray(a)
|
216 | }
|
217 | return _isNumber(a[0]) && _isNumber(a[1])
|
218 | }
|
219 |
|
220 | function _3d (a) {
|
221 |
|
222 | if (a.constructor !== Array) {
|
223 | a = _objectToArray(a)
|
224 | }
|
225 | return _isNumber(a[0]) && _isNumber(a[1]) && _isNumber(a[2])
|
226 | }
|
227 |
|
228 | function _containsOnlyNumbers (a) {
|
229 |
|
230 | if (!Array.isArray(a)) {
|
231 | a = _objectToArray(a)
|
232 | }
|
233 | return a.every(_isNumber)
|
234 | }
|
235 |
|
236 | function _parametricLine (a) {
|
237 | if (a.constructor !== Array) {
|
238 | a = _objectToArray(a)
|
239 | }
|
240 | return _isNumber(a[0]) && _isNumber(a[1]) && _isNumber(a[2]) &&
|
241 | _isNumber(a[3]) && _isNumber(a[4]) && _isNumber(a[5])
|
242 | }
|
243 |
|
244 | function _objectToArray (o) {
|
245 | const keys = Object.keys(o)
|
246 | const a = []
|
247 | for (let i = 0; i < keys.length; i++) {
|
248 | a.push(o[keys[i]])
|
249 | }
|
250 | return a
|
251 | }
|
252 |
|
253 | function _pairwise (a) {
|
254 |
|
255 | if (a[0].length === 2 && _isNumber(a[0][0]) && _isNumber(a[0][1])) {
|
256 | if (a.some(aI => aI.length !== 2 || !_isNumber(aI[0]) || !_isNumber(aI[1]))) {
|
257 | return false
|
258 | }
|
259 | } else if (a[0].length === 3 && _isNumber(a[0][0]) && _isNumber(a[0][1]) && _isNumber(a[0][2])) {
|
260 | if (a.some(aI => aI.length !== 3 || !_isNumber(aI[0]) || !_isNumber(aI[1]) || !_isNumber(aI[2]))) {
|
261 | return false
|
262 | }
|
263 | } else {
|
264 | return false
|
265 | }
|
266 | return true
|
267 | }
|
268 |
|
269 | function _distancePointLine2D (x, y, a, b, c) {
|
270 | const num = abs(addScalar(addScalar(multiplyScalar(a, x), multiplyScalar(b, y)), c))
|
271 | const den = sqrt(addScalar(multiplyScalar(a, a), multiplyScalar(b, b)))
|
272 | return divideScalar(num, den)
|
273 | }
|
274 |
|
275 | function _distancePointLine3D (x, y, z, x0, y0, z0, a, b, c) {
|
276 | let num = [subtract(multiplyScalar(subtract(y0, y), c), multiplyScalar(subtract(z0, z), b)),
|
277 | subtract(multiplyScalar(subtract(z0, z), a), multiplyScalar(subtract(x0, x), c)),
|
278 | subtract(multiplyScalar(subtract(x0, x), b), multiplyScalar(subtract(y0, y), a))]
|
279 | num = sqrt(addScalar(addScalar(multiplyScalar(num[0], num[0]), multiplyScalar(num[1], num[1])), multiplyScalar(num[2], num[2])))
|
280 | const den = sqrt(addScalar(addScalar(multiplyScalar(a, a), multiplyScalar(b, b)), multiplyScalar(c, c)))
|
281 | return divideScalar(num, den)
|
282 | }
|
283 |
|
284 | function _euclideanDistance (x, y) {
|
285 | const vectorSize = x.length
|
286 | let result = 0
|
287 | let diff = 0
|
288 | for (let i = 0; i < vectorSize; i++) {
|
289 | diff = subtract(x[i], y[i])
|
290 | result = addScalar(multiplyScalar(diff, diff), result)
|
291 | }
|
292 | return sqrt(result)
|
293 | }
|
294 |
|
295 | function _distancePairwise (a) {
|
296 | const result = []
|
297 | let pointA = []
|
298 | let pointB = []
|
299 | for (let i = 0; i < a.length - 1; i++) {
|
300 | for (let j = i + 1; j < a.length; j++) {
|
301 | if (a[0].length === 2) {
|
302 | pointA = [a[i][0], a[i][1]]
|
303 | pointB = [a[j][0], a[j][1]]
|
304 | } else if (a[0].length === 3) {
|
305 | pointA = [a[i][0], a[i][1], a[i][2]]
|
306 | pointB = [a[j][0], a[j][1], a[j][2]]
|
307 | }
|
308 | result.push(_euclideanDistance(pointA, pointB))
|
309 | }
|
310 | }
|
311 | return result
|
312 | }
|
313 | })
|