simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

distance.js (14098B)


      1 import { isBigNumber } from '../../utils/is.js';
      2 import { factory } from '../../utils/factory.js';
      3 var name = 'distance';
      4 var dependencies = ['typed', 'addScalar', 'subtract', 'divideScalar', 'multiplyScalar', 'unaryMinus', 'sqrt', 'abs'];
      5 export var createDistance = /* #__PURE__ */factory(name, dependencies, _ref => {
      6   var {
      7     typed,
      8     addScalar,
      9     subtract,
     10     multiplyScalar,
     11     divideScalar,
     12     unaryMinus,
     13     sqrt,
     14     abs
     15   } = _ref;
     16 
     17   /**
     18     * Calculates:
     19     *    The eucledian distance between two points in N-dimensional spaces.
     20     *    Distance between point and a line in 2 and 3 dimensional spaces.
     21     *    Pairwise distance between a set of 2D or 3D points
     22     * NOTE:
     23     *    When substituting coefficients of a line(a, b and c), use ax + by + c = 0 instead of ax + by = c
     24     *    For parametric equation of a 3D line, x0, y0, z0, a, b, c are from: (x−x0, y−y0, z−z0) = t(a, b, c)
     25     *
     26     * Syntax:
     27     *    math.distance([x1, y1], [x2, y2])
     28     *-   math.distance({pointOneX: 4, pointOneY: 5}, {pointTwoX: 2, pointTwoY: 7})
     29     *    math.distance([x1, y1, z1], [x2, y2, z2])
     30     *    math.distance({pointOneX: 4, pointOneY: 5, pointOneZ: 8}, {pointTwoX: 2, pointTwoY: 7, pointTwoZ: 9})
     31     *    math.distance([x1, y1, ... , N1], [x2, y2, ... , N2])
     32     *    math.distance([[A], [B], [C]...])
     33     *    math.distance([x1, y1], [LinePtX1, LinePtY1], [LinePtX2, LinePtY2])
     34     *    math.distance({pointX: 1, pointY: 4}, {lineOnePtX: 6, lineOnePtY: 3}, {lineTwoPtX: 2, lineTwoPtY: 8})
     35     *    math.distance([x1, y1, z1], [LinePtX1, LinePtY1, LinePtZ1], [LinePtX2, LinePtY2, LinePtZ2])
     36     *    math.distance({pointX: 1, pointY: 4, pointZ: 7}, {lineOnePtX: 6, lineOnePtY: 3, lineOnePtZ: 4}, {lineTwoPtX: 2, lineTwoPtY: 8, lineTwoPtZ: 5})
     37     *    math.distance([x1, y1], [xCoeffLine, yCoeffLine, constant])
     38     *    math.distance({pointX: 10, pointY: 10}, {xCoeffLine: 8, yCoeffLine: 1, constant: 3})
     39     *    math.distance([x1, y1, z1], [x0, y0, z0, a-tCoeff, b-tCoeff, c-tCoeff]) point and parametric equation of 3D line
     40     *    math.distance([x, y, z], [x0, y0, z0, a, b, c])
     41     *    math.distance({pointX: 2, pointY: 5, pointZ: 9}, {x0: 4, y0: 6, z0: 3, a: 4, b: 2, c: 0})
     42     *
     43     * Examples:
     44     *    math.distance([0,0], [4,4])                     // Returns 5.6569
     45     *    math.distance(
     46     *     {pointOneX: 0, pointOneY: 0},
     47     *     {pointTwoX: 10, pointTwoY: 10})                // Returns 14.142135623730951
     48     *    math.distance([1, 0, 1], [4, -2, 2])            // Returns 3.74166
     49     *    math.distance(
     50     *     {pointOneX: 4, pointOneY: 5, pointOneZ: 8},
     51     *     {pointTwoX: 2, pointTwoY: 7, pointTwoZ: 9})    // Returns 3
     52     *    math.distance([1, 0, 1, 0], [0, -1, 0, -1])     // Returns 2
     53     *    math.distance([[1, 2], [1, 2], [1, 3]])         // Returns [0, 1, 1]
     54     *    math.distance([[1,2,4], [1,2,6], [8,1,3]])      // Returns [2, 7.14142842854285, 7.681145747868608]
     55     *    math.distance([10, 10], [8, 1, 3])              // Returns 11.535230316796387
     56     *    math.distance([10, 10], [2, 3], [-8, 0])        // Returns 8.759953130362847
     57     *    math.distance(
     58     *     {pointX: 1, pointY: 4},
     59     *     {lineOnePtX: 6, lineOnePtY: 3},
     60     *     {lineTwoPtX: 2, lineTwoPtY: 8})                // Returns 2.720549372624744
     61     *    math.distance([2, 3, 1], [1, 1, 2, 5, 0, 1])    // Returns 2.3204774044612857
     62     *    math.distance(
     63     *     {pointX: 2, pointY: 3, pointZ: 1},
     64     *     {x0: 1, y0: 1, z0: 2, a: 5, b: 0, c: 1}        // Returns 2.3204774044612857
     65     *
     66     * @param {Array | Matrix | Object} x    Co-ordinates of first point
     67     * @param {Array | Matrix | Object} y    Co-ordinates of second point
     68     * @return {Number | BigNumber} Returns the distance from two/three points
     69   */
     70   return typed(name, {
     71     'Array, Array, Array': function ArrayArrayArray(x, y, z) {
     72       // Point to Line 2D (x=Point, y=LinePoint1, z=LinePoint2)
     73       if (x.length === 2 && y.length === 2 && z.length === 2) {
     74         if (!_2d(x)) {
     75           throw new TypeError('Array with 2 numbers or BigNumbers expected for first argument');
     76         }
     77 
     78         if (!_2d(y)) {
     79           throw new TypeError('Array with 2 numbers or BigNumbers expected for second argument');
     80         }
     81 
     82         if (!_2d(z)) {
     83           throw new TypeError('Array with 2 numbers or BigNumbers expected for third argument');
     84         }
     85 
     86         var m = divideScalar(subtract(z[1], z[0]), subtract(y[1], y[0]));
     87         var xCoeff = multiplyScalar(multiplyScalar(m, m), y[0]);
     88         var yCoeff = unaryMinus(multiplyScalar(m, y[0]));
     89         var constant = x[1];
     90         return _distancePointLine2D(x[0], x[1], xCoeff, yCoeff, constant);
     91       } else {
     92         throw new TypeError('Invalid Arguments: Try again');
     93       }
     94     },
     95     'Object, Object, Object': function ObjectObjectObject(x, y, z) {
     96       if (Object.keys(x).length === 2 && Object.keys(y).length === 2 && Object.keys(z).length === 2) {
     97         if (!_2d(x)) {
     98           throw new TypeError('Values of pointX and pointY should be numbers or BigNumbers');
     99         }
    100 
    101         if (!_2d(y)) {
    102           throw new TypeError('Values of lineOnePtX and lineOnePtY should be numbers or BigNumbers');
    103         }
    104 
    105         if (!_2d(z)) {
    106           throw new TypeError('Values of lineTwoPtX and lineTwoPtY should be numbers or BigNumbers');
    107         }
    108 
    109         if ('pointX' in x && 'pointY' in x && 'lineOnePtX' in y && 'lineOnePtY' in y && 'lineTwoPtX' in z && 'lineTwoPtY' in z) {
    110           var m = divideScalar(subtract(z.lineTwoPtY, z.lineTwoPtX), subtract(y.lineOnePtY, y.lineOnePtX));
    111           var xCoeff = multiplyScalar(multiplyScalar(m, m), y.lineOnePtX);
    112           var yCoeff = unaryMinus(multiplyScalar(m, y.lineOnePtX));
    113           var constant = x.pointX;
    114           return _distancePointLine2D(x.pointX, x.pointY, xCoeff, yCoeff, constant);
    115         } else {
    116           throw new TypeError('Key names do not match');
    117         }
    118       } else {
    119         throw new TypeError('Invalid Arguments: Try again');
    120       }
    121     },
    122     'Array, Array': function ArrayArray(x, y) {
    123       // Point to Line 2D (x=[pointX, pointY], y=[x-coeff, y-coeff, const])
    124       if (x.length === 2 && y.length === 3) {
    125         if (!_2d(x)) {
    126           throw new TypeError('Array with 2 numbers or BigNumbers expected for first argument');
    127         }
    128 
    129         if (!_3d(y)) {
    130           throw new TypeError('Array with 3 numbers or BigNumbers expected for second argument');
    131         }
    132 
    133         return _distancePointLine2D(x[0], x[1], y[0], y[1], y[2]);
    134       } else if (x.length === 3 && y.length === 6) {
    135         // Point to Line 3D
    136         if (!_3d(x)) {
    137           throw new TypeError('Array with 3 numbers or BigNumbers expected for first argument');
    138         }
    139 
    140         if (!_parametricLine(y)) {
    141           throw new TypeError('Array with 6 numbers or BigNumbers expected for second argument');
    142         }
    143 
    144         return _distancePointLine3D(x[0], x[1], x[2], y[0], y[1], y[2], y[3], y[4], y[5]);
    145       } else if (x.length === y.length && x.length > 0) {
    146         // Point to Point N-dimensions
    147         if (!_containsOnlyNumbers(x)) {
    148           throw new TypeError('All values of an array should be numbers or BigNumbers');
    149         }
    150 
    151         if (!_containsOnlyNumbers(y)) {
    152           throw new TypeError('All values of an array should be numbers or BigNumbers');
    153         }
    154 
    155         return _euclideanDistance(x, y);
    156       } else {
    157         throw new TypeError('Invalid Arguments: Try again');
    158       }
    159     },
    160     'Object, Object': function ObjectObject(x, y) {
    161       if (Object.keys(x).length === 2 && Object.keys(y).length === 3) {
    162         if (!_2d(x)) {
    163           throw new TypeError('Values of pointX and pointY should be numbers or BigNumbers');
    164         }
    165 
    166         if (!_3d(y)) {
    167           throw new TypeError('Values of xCoeffLine, yCoeffLine and constant should be numbers or BigNumbers');
    168         }
    169 
    170         if ('pointX' in x && 'pointY' in x && 'xCoeffLine' in y && 'yCoeffLine' in y && 'constant' in y) {
    171           return _distancePointLine2D(x.pointX, x.pointY, y.xCoeffLine, y.yCoeffLine, y.constant);
    172         } else {
    173           throw new TypeError('Key names do not match');
    174         }
    175       } else if (Object.keys(x).length === 3 && Object.keys(y).length === 6) {
    176         // Point to Line 3D
    177         if (!_3d(x)) {
    178           throw new TypeError('Values of pointX, pointY and pointZ should be numbers or BigNumbers');
    179         }
    180 
    181         if (!_parametricLine(y)) {
    182           throw new TypeError('Values of x0, y0, z0, a, b and c should be numbers or BigNumbers');
    183         }
    184 
    185         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) {
    186           return _distancePointLine3D(x.pointX, x.pointY, x.pointZ, y.x0, y.y0, y.z0, y.a, y.b, y.c);
    187         } else {
    188           throw new TypeError('Key names do not match');
    189         }
    190       } else if (Object.keys(x).length === 2 && Object.keys(y).length === 2) {
    191         // Point to Point 2D
    192         if (!_2d(x)) {
    193           throw new TypeError('Values of pointOneX and pointOneY should be numbers or BigNumbers');
    194         }
    195 
    196         if (!_2d(y)) {
    197           throw new TypeError('Values of pointTwoX and pointTwoY should be numbers or BigNumbers');
    198         }
    199 
    200         if ('pointOneX' in x && 'pointOneY' in x && 'pointTwoX' in y && 'pointTwoY' in y) {
    201           return _euclideanDistance([x.pointOneX, x.pointOneY], [y.pointTwoX, y.pointTwoY]);
    202         } else {
    203           throw new TypeError('Key names do not match');
    204         }
    205       } else if (Object.keys(x).length === 3 && Object.keys(y).length === 3) {
    206         // Point to Point 3D
    207         if (!_3d(x)) {
    208           throw new TypeError('Values of pointOneX, pointOneY and pointOneZ should be numbers or BigNumbers');
    209         }
    210 
    211         if (!_3d(y)) {
    212           throw new TypeError('Values of pointTwoX, pointTwoY and pointTwoZ should be numbers or BigNumbers');
    213         }
    214 
    215         if ('pointOneX' in x && 'pointOneY' in x && 'pointOneZ' in x && 'pointTwoX' in y && 'pointTwoY' in y && 'pointTwoZ' in y) {
    216           return _euclideanDistance([x.pointOneX, x.pointOneY, x.pointOneZ], [y.pointTwoX, y.pointTwoY, y.pointTwoZ]);
    217         } else {
    218           throw new TypeError('Key names do not match');
    219         }
    220       } else {
    221         throw new TypeError('Invalid Arguments: Try again');
    222       }
    223     },
    224     Array: function Array(arr) {
    225       if (!_pairwise(arr)) {
    226         throw new TypeError('Incorrect array format entered for pairwise distance calculation');
    227       }
    228 
    229       return _distancePairwise(arr);
    230     }
    231   });
    232 
    233   function _isNumber(a) {
    234     // distance supports numbers and bignumbers
    235     return typeof a === 'number' || isBigNumber(a);
    236   }
    237 
    238   function _2d(a) {
    239     // checks if the number of arguments are correct in count and are valid (should be numbers)
    240     if (a.constructor !== Array) {
    241       a = _objectToArray(a);
    242     }
    243 
    244     return _isNumber(a[0]) && _isNumber(a[1]);
    245   }
    246 
    247   function _3d(a) {
    248     // checks if the number of arguments are correct in count and are valid (should be numbers)
    249     if (a.constructor !== Array) {
    250       a = _objectToArray(a);
    251     }
    252 
    253     return _isNumber(a[0]) && _isNumber(a[1]) && _isNumber(a[2]);
    254   }
    255 
    256   function _containsOnlyNumbers(a) {
    257     // checks if the number of arguments are correct in count and are valid (should be numbers)
    258     if (!Array.isArray(a)) {
    259       a = _objectToArray(a);
    260     }
    261 
    262     return a.every(_isNumber);
    263   }
    264 
    265   function _parametricLine(a) {
    266     if (a.constructor !== Array) {
    267       a = _objectToArray(a);
    268     }
    269 
    270     return _isNumber(a[0]) && _isNumber(a[1]) && _isNumber(a[2]) && _isNumber(a[3]) && _isNumber(a[4]) && _isNumber(a[5]);
    271   }
    272 
    273   function _objectToArray(o) {
    274     var keys = Object.keys(o);
    275     var a = [];
    276 
    277     for (var i = 0; i < keys.length; i++) {
    278       a.push(o[keys[i]]);
    279     }
    280 
    281     return a;
    282   }
    283 
    284   function _pairwise(a) {
    285     // checks for valid arguments passed to _distancePairwise(Array)
    286     if (a[0].length === 2 && _isNumber(a[0][0]) && _isNumber(a[0][1])) {
    287       if (a.some(aI => aI.length !== 2 || !_isNumber(aI[0]) || !_isNumber(aI[1]))) {
    288         return false;
    289       }
    290     } else if (a[0].length === 3 && _isNumber(a[0][0]) && _isNumber(a[0][1]) && _isNumber(a[0][2])) {
    291       if (a.some(aI => aI.length !== 3 || !_isNumber(aI[0]) || !_isNumber(aI[1]) || !_isNumber(aI[2]))) {
    292         return false;
    293       }
    294     } else {
    295       return false;
    296     }
    297 
    298     return true;
    299   }
    300 
    301   function _distancePointLine2D(x, y, a, b, c) {
    302     var num = abs(addScalar(addScalar(multiplyScalar(a, x), multiplyScalar(b, y)), c));
    303     var den = sqrt(addScalar(multiplyScalar(a, a), multiplyScalar(b, b)));
    304     return divideScalar(num, den);
    305   }
    306 
    307   function _distancePointLine3D(x, y, z, x0, y0, z0, a, b, c) {
    308     var num = [subtract(multiplyScalar(subtract(y0, y), c), multiplyScalar(subtract(z0, z), b)), subtract(multiplyScalar(subtract(z0, z), a), multiplyScalar(subtract(x0, x), c)), subtract(multiplyScalar(subtract(x0, x), b), multiplyScalar(subtract(y0, y), a))];
    309     num = sqrt(addScalar(addScalar(multiplyScalar(num[0], num[0]), multiplyScalar(num[1], num[1])), multiplyScalar(num[2], num[2])));
    310     var den = sqrt(addScalar(addScalar(multiplyScalar(a, a), multiplyScalar(b, b)), multiplyScalar(c, c)));
    311     return divideScalar(num, den);
    312   }
    313 
    314   function _euclideanDistance(x, y) {
    315     var vectorSize = x.length;
    316     var result = 0;
    317     var diff = 0;
    318 
    319     for (var i = 0; i < vectorSize; i++) {
    320       diff = subtract(x[i], y[i]);
    321       result = addScalar(multiplyScalar(diff, diff), result);
    322     }
    323 
    324     return sqrt(result);
    325   }
    326 
    327   function _distancePairwise(a) {
    328     var result = [];
    329     var pointA = [];
    330     var pointB = [];
    331 
    332     for (var i = 0; i < a.length - 1; i++) {
    333       for (var j = i + 1; j < a.length; j++) {
    334         if (a[0].length === 2) {
    335           pointA = [a[i][0], a[i][1]];
    336           pointB = [a[j][0], a[j][1]];
    337         } else if (a[0].length === 3) {
    338           pointA = [a[i][0], a[i][1], a[i][2]];
    339           pointB = [a[j][0], a[j][1], a[j][2]];
    340         }
    341 
    342         result.push(_euclideanDistance(pointA, pointB));
    343       }
    344     }
    345 
    346     return result;
    347   }
    348 });