calcDelaunay.mjs

import get from 'lodash-es/get.js'
import size from 'lodash-es/size.js'
import cloneDeep from 'lodash-es/cloneDeep.js'
import isestr from 'wsemi/src/isestr.mjs'
import isearr from 'wsemi/src/isearr.mjs'
import isbol from 'wsemi/src/isbol.mjs'
import iseobj from 'wsemi/src/iseobj.mjs'
import isnum from 'wsemi/src/isnum.mjs'
import ptsXYtoArr from './ptsXYtoArr.mjs'
import { Delaunay } from 'd3-delaunay'


/**
 * 計算Delaunay三角網格
 *
 * Unit Test: {@link https://github.com/yuda-lyu/w-gis/blob/master/test/calcDelaunay.test.mjs Github}
 * @memberOf w-gis
 * @param {Array} points 輸入二維座標加觀測數據點陣列,為[{x:x1,y:y1,z:z1},{x:x2,y:y2,z:z2},...]點物件之陣列
 * @param {Object} [opt={}] 輸入設定物件,預設{}
 * @param {String} [opt.keyX='x'] 輸入點物件之x欄位字串,為座標,預設'x'
 * @param {String} [opt.keyY='y'] 輸入點物件之y欄位字串,為座標,預設'y'
 * @param {Boolean} [opt.withFinder=false] 輸入是否多回傳查詢x,y座標所在網格函數布林值,預設false
 * @param {Boolean} [opt.withInst=false] 輸入是否多回傳D3的Delaunay建構物件布林值,預設false
 * @returns {Object} 回傳三角網格資訊物件,triangles為三角網格之節點指標,若發生錯誤則回傳錯誤訊息物件
 * @example
 *
 * let ps
 * let o
 * let r
 *
 * ps = [{ x: 243, y: 206 }, { x: 233, y: 225 }, { x: 21, y: 325 }, { x: 953, y: 283 }, { x: 1092, y: 290 }, { x: 744, y: 200 }, { x: 174, y: 3 }, { x: 537, y: 368 }, { x: 1151, y: 371, ext: 'abc' }, { x: 814, y: 252 }]
 * r = calcDelaunay(ps)
 * console.log(r)
 * // => {
 * //   triangles: [
 * //     { n0: 5, n1: 9, n2: 3 },
 * //     { n0: 9, n1: 4, n2: 3 },
 * //     { n0: 9, n1: 8, n2: 4 },
 * //     { n0: 4, n1: 8, n2: 3 },
 * //     { n0: 5, n1: 7, n2: 9 },
 * //     { n0: 9, n1: 7, n2: 8 },
 * //     { n0: 0, n1: 7, n2: 5 },
 * //     { n0: 6, n1: 0, n2: 5 },
 * //     { n0: 0, n1: 1, n2: 7 },
 * //     { n0: 3, n1: 6, n2: 5 }
 * //   ]
 * // }
 *
 * ps = [{ x: 243, y: 206 }, { x: 233, y: 225 }, { x: 21, y: 325 }, { x: 953, y: 283 }, { x: 1092, y: 290 }, { x: 744, y: 200 }, { x: 174, y: 3 }, { x: 537, y: 368 }, { x: 1151, y: 371, ext: 'abc' }, { x: 814, y: 252 }]
 * o = calcDelaunay(ps, { withFinder: true })
 * r = o.funFindIn({ x: 1151, y: 371 })
 * console.log(r)
 * // => 8
 * r = o.funFindIn({ x: 1071, y: 371 }, { returnPoint: true })
 * console.log(r)
 * // => { x: 1151, y: 371, ext: 'abc' }
 * r = o.funFindIn({ x: 1061, y: 371 })
 * console.log(r)
 * // => 4
 *
 */
function calcDelaunay(points, opt = {}) {

    //check points
    if (!isearr(points)) {
        return {
            err: 'points is not an effective array'
        }
    }

    //keyX
    let keyX = get(opt, 'keyX')
    if (!isestr(keyX)) {
        keyX = 'x'
    }

    //keyY
    let keyY = get(opt, 'keyY')
    if (!isestr(keyY)) {
        keyY = 'y'
    }

    //withFinder
    let withFinder = get(opt, 'withFinder')
    if (!isbol(withFinder)) {
        withFinder = false
    }

    //withInst
    let withInst = get(opt, 'withInst')
    if (!isbol(withInst)) {
        withInst = false
    }

    //pointsOri
    let pointsOri = cloneDeep(points)

    //keyInd
    let keyInd = 'ind'

    //ptsXYtoArr
    points = ptsXYtoArr(points, { keyX, keyY, keyInd })

    //check points
    if (size(points) === 0) {
        return {
            err: 'points has no effective data'
        }
    }
    // console.log('ptsXYtoArr points', points)

    //intDny
    let intDny = Delaunay.from(points, d => d.x, d => d.y)
    // console.log('intDny', intDny)

    //vtriangles, 各三角網格之節點指標
    let vtriangles = []
    for (let i = 0; i < size(points); i++) {
        let n0 = intDny.triangles[i * 3 + 0]
        let n1 = intDny.triangles[i * 3 + 1]
        let n2 = intDny.triangles[i * 3 + 2]
        // console.log('n0', n0, 'n1', n1, 'n2', n2)
        vtriangles.push({ n0, n1, n2, })
    }

    //funFindIn
    let funFindIn = (p, opt = {}) => {
        if (!iseobj(p)) {
            throw new Error(`p[${p}] is not an effective object`)
        }
        let x = get(p, 'x')
        if (!isnum(x)) {
            throw new Error(`x[${x}] is not a number`)
        }
        let y = get(p, 'y')
        if (!isnum(y)) {
            throw new Error(`y[${y}] is not a number`)
        }
        let returnPoint = get(opt, 'returnPoint')
        if (!isbol(returnPoint)) {
            returnPoint = false
        }
        let r = intDny.find(x, y) //基於Delaunay查找最近點
        if (returnPoint) {
            r = get(pointsOri, r)
        }
        return r
    }

    //r
    let r = {
        triangles: vtriangles,
    }

    if (withFinder) {
        r.funFindIn = funFindIn
    }
    if (withInst) {
        r.inst = intDny
    }

    return r
}


export default calcDelaunay