mergeByDepthStartEnd.mjs

import get from 'lodash-es/get.js'
import each from 'lodash-es/each.js'
import map from 'lodash-es/map.js'
import size from 'lodash-es/size.js'
import join from 'lodash-es/join.js'
import sortBy from 'lodash-es/sortBy.js'
import pullAt from 'lodash-es/pullAt.js'
import filter from 'lodash-es/filter.js'
import cloneDeep from 'lodash-es/cloneDeep.js'
import cdbl from 'wsemi/src/cdbl.mjs'
import isestr from 'wsemi/src/isestr.mjs'
import isnum from 'wsemi/src/isnum.mjs'
import isarr from 'wsemi/src/isarr.mjs'
import isearr from 'wsemi/src/isearr.mjs'
import iseobj from 'wsemi/src/iseobj.mjs'
import isbol from 'wsemi/src/isbol.mjs'
import isfun from 'wsemi/src/isfun.mjs'
import judge from './judge.mjs'
import checkDepthStartEnd from './checkDepthStartEnd.mjs'


/**
 * 基於各樣本之起訖深度與指定欄位值進行合併
 *
 * Unit Test: {@link https://github.com/yuda-lyu/w-geo/blob/master/test/mergeByDepthStartEnd.test.js Github}
 * @memberOf w-geo
 * @param {Array} rows 輸入數據陣列,各數據為物件,至少需包含起始深度(depthStart)與結束深度(depthEnd),深度單位為m
 * @param {Object} [opt={}] 輸入設定物件,預設{}
 * @param {String} [opt.keyDepthStart='depthStart'] 輸入欲儲存之起始深度欄位鍵值字串,預設'depthStart'
 * @param {String} [opt.keyDepthEnd='depthEnd'] 輸入欲儲存之結束深度欄位鍵值字串,預設'depthEnd'
 * @param {String} [opt.keyValue='value'] 輸入指定偵測欄位鍵值字串,預設'value'
 * @param {Boolean} [opt.saveFromInds=false] 輸入是否儲存合併來源指標布林值,預設false
 * @param {String} [opt.keyInd='ind'] 輸入標記來源指標欄位字串,預設'ind'
 * @param {String} [opt.keyFromInds='fromInds'] 輸入儲存來源指標欄位字串,預設'fromInds'
 * @param {String} [opt.typeDetect='fromInds'] 輸入偵測合併機制字串,可選'sequence'、'iterate',前者代表一次性偵測合併,後者代表迭代多次偵測合併,預設'sequence'
 * @param {Function} [opt.funMerge=null] 輸入字定義合併函數,輸入為v0與v1上下兩土層參數物件,回傳合併後參數物件,預設null
 * @param {String} [opt.typeMerge='down'] 輸入合併方向字串,可選'down'、'up',前者代表以下層土層參數為主,後者以上層土層參數為主,預設'down'
 * @returns {Array} 回傳合併後的數據陣列
 * @example
 *
 * let rows
 * let rows
 *
 * rows = [
 *     {
 *         depthStart: 0,
 *         depthEnd: 5,
 *         value: 'a',
 *         ext: 12.3,
 *     },
 *     {
 *         depthStart: 5,
 *         depthEnd: 7,
 *         value: 'b',
 *         ext: 12.4,
 *     },
 *     {
 *         depthStart: 7,
 *         depthEnd: 11,
 *         value: 'b',
 *         ext: 2.5,
 *     },
 *     {
 *         depthStart: 11,
 *         depthEnd: 15,
 *         value: 'a',
 *         ext: 2.3,
 *     },
 * ]
 * rows = mergeByDepthStartEnd(rows)
 * console.log(JSON.stringify(rows, null, 2))
 * // => [
 * //   {
 * //     "depthStart": 0,
 * //     "depthEnd": 5,
 * //     "value": "a",
 * //     "ext": 12.3
 * //   },
 * //   {
 * //     "depthStart": 5,
 * //     "depthEnd": 11,
 * //     "value": "b",
 * //     "ext": 2.5
 * //   },
 * //   {
 * //     "depthStart": 11,
 * //     "depthEnd": 15,
 * //     "value": "a",
 * //     "ext": 2.3
 * //   }
 * // ]
 *
 * rows = [
 *     {
 *         ds: 0,
 *         de: 5,
 *         value: 'a',
 *         ext: 12.3,
 *     },
 *     {
 *         ds: 5,
 *         de: 7,
 *         value: 'b',
 *         ext: 12.4,
 *     },
 *     {
 *         ds: 7,
 *         de: 11,
 *         value: 'b',
 *         ext: 2.5,
 *     },
 *     {
 *         ds: 11,
 *         de: 15,
 *         value: 'a',
 *         ext: 2.3,
 *     },
 * ]
 * rows = mergeByDepthStartEnd(rows, { keyDepthStart: 'ds', keyDepthEnd: 'de' })
 * console.log(JSON.stringify(rows, null, 2))
 * // => [
 * //   {
 * //     "ds": 0,
 * //     "de": 5,
 * //     "value": "a",
 * //     "ext": 12.3
 * //   },
 * //   {
 * //     "ds": 5,
 * //     "de": 11,
 * //     "value": "b",
 * //     "ext": 2.5
 * //   },
 * //   {
 * //     "ds": 11,
 * //     "de": 15,
 * //     "value": "a",
 * //     "ext": 2.3
 * //   }
 * // ]
 *
 * rows = [
 *     {
 *         depthStart: 0,
 *         depthEnd: 5,
 *         data: 'a',
 *         ext: 12.3,
 *     },
 *     {
 *         depthStart: 5,
 *         depthEnd: 7,
 *         data: 'b',
 *         ext: 12.4,
 *     },
 *     {
 *         depthStart: 7,
 *         depthEnd: 11,
 *         data: 'b',
 *         ext: 2.5,
 *     },
 *     {
 *         depthStart: 11,
 *         depthEnd: 15,
 *         data: 'a',
 *         ext: 2.3,
 *     },
 * ]
 * rows = mergeByDepthStartEnd(rows, { keyValue: 'data' })
 * console.log(JSON.stringify(rows, null, 2))
 * // => [
 * //   {
 * //     "depthStart": 0,
 * //     "depthEnd": 5,
 * //     "data": "a",
 * //     "ext": 12.3
 * //   },
 * //   {
 * //     "depthStart": 5,
 * //     "depthEnd": 11,
 * //     "data": "b",
 * //     "ext": 2.5
 * //   },
 * //   {
 * //     "depthStart": 11,
 * //     "depthEnd": 15,
 * //     "data": "a",
 * //     "ext": 2.3
 * //   }
 * // ]
 *
 * rows = [
 *     {
 *         depthStart: 0,
 *         depthEnd: 5,
 *         value: 'a',
 *         ext: 12.3,
 *     },
 *     {
 *         depthStart: 5,
 *         depthEnd: 7,
 *         value: 'b',
 *         ext: 12.4,
 *     },
 *     {
 *         depthStart: 7,
 *         depthEnd: 11,
 *         value: 'b',
 *         ext: 2.5,
 *     },
 *     {
 *         depthStart: 11,
 *         depthEnd: 15,
 *         value: 'b',
 *         ext: 2.3,
 *     },
 *     {
 *         depthStart: 15,
 *         depthEnd: 18,
 *         value: 'a',
 *         ext: 7.9,
 *     },
 * ]
 * rows = mergeByDepthStartEnd(rows, {
 *     funMerge: (v0, v1) => {
 *         return {
 *             // depthStart: null,
 *             // depthEnd: null,
 *             value: v1.value,
 *             ext: v1.ext,
 *             _v0: v0,
 *             _v1: v1,
 *         }
 *     }
 * })
 * console.log(JSON.stringify(rows, null, 2))
 * // => [
 * //   {
 * //     "depthStart": 0,
 * //     "depthEnd": 5,
 * //     "value": "a",
 * //     "ext": 12.3
 * //   },
 * //   {
 * //     "value": "b",
 * //     "ext": 2.3,
 * //     "_v0": {
 * //       "value": "b",
 * //       "ext": 2.5,
 * //       "_v0": {
 * //         "depthStart": 5,
 * //         "depthEnd": 7,
 * //         "value": "b",
 * //         "ext": 12.4
 * //       },
 * //       "_v1": {
 * //         "depthStart": 7,
 * //         "depthEnd": 11,
 * //         "value": "b",
 * //         "ext": 2.5
 * //       },
 * //       "depthStart": 5,
 * //       "depthEnd": 11
 * //     },
 * //     "_v1": {
 * //       "depthStart": 11,
 * //       "depthEnd": 15,
 * //       "value": "b",
 * //       "ext": 2.3
 * //     },
 * //     "depthStart": 5,
 * //     "depthEnd": 15
 * //   },
 * //   {
 * //     "depthStart": 15,
 * //     "depthEnd": 18,
 * //     "value": "a",
 * //     "ext": 7.9
 * //   }
 * // ]
 *
 * rows = [
 *     {
 *         depthStart: 0,
 *         depthEnd: 5,
 *         value: 'a',
 *         ext: 12.3,
 *     },
 *     {
 *         depthStart: 5,
 *         depthEnd: 7,
 *         value: 'b',
 *         ext: 12.4,
 *     },
 *     {
 *         depthStart: 7,
 *         depthEnd: 11,
 *         value: 'b',
 *         ext: 2.5,
 *     },
 *     {
 *         depthStart: 11,
 *         depthEnd: 15,
 *         value: 'b',
 *         ext: 2.3,
 *     },
 *     {
 *         depthStart: 15,
 *         depthEnd: 18,
 *         value: 'a',
 *         ext: 7.9,
 *     },
 * ]
 * rows = mergeByDepthStartEnd(rows, { typeMerge: 'up' })
 * console.log(JSON.stringify(rows, null, 2))
 * // => [
 * //   {
 * //     "depthStart": 0,
 * //     "depthEnd": 5,
 * //     "value": "a",
 * //     "ext": 12.3
 * //   },
 * //   {
 * //     "depthStart": 5,
 * //     "depthEnd": 15,
 * //     "value": "b",
 * //     "ext": 12.4
 * //   },
 * //   {
 * //     "depthStart": 15,
 * //     "depthEnd": 18,
 * //     "value": "a",
 * //     "ext": 7.9
 * //   }
 * // ]
 *
 */
function mergeByDepthStartEnd(rows, opt = {}) {

    //check
    if (!isearr(rows)) {
        throw new Error(`無有效數據`)
    }

    // //keyDepthStart
    // let keyDepthStart = get(opt, 'keyDepthStart')
    // if (!isestr(keyDepthStart)) {
    //     keyDepthStart = 'depthStart'
    // }

    // //keyDepthEnd
    // let keyDepthEnd = get(opt, 'keyDepthEnd')
    // if (!isestr(keyDepthEnd)) {
    //     keyDepthEnd = 'depthEnd'
    // }

    // //keyValue
    // let keyValue = get(opt, 'keyValue')
    // if (!isestr(keyValue)) {
    //     keyValue = 'value'
    // }

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

    // //keyInd
    // let keyInd = get(opt, 'keyInd')
    // if (!isestr(keyInd)) {
    //     keyInd = 'ind'
    // }

    // //keyFromInds
    // let keyFromInds = get(opt, 'keyFromInds')
    // if (!isestr(keyFromInds)) {
    //     keyFromInds = 'fromInds'
    // }

    // //funMerge
    // let funMerge = get(opt, 'funMerge')
    // let useFunMerge = isfun(funMerge)

    // //typeMerge
    // let typeMerge = get(opt, 'typeMerge')
    // if (typeMerge !== 'up' && typeMerge !== 'down') {
    //     typeMerge = 'down'
    // }

    // //cloneDeep
    // rows = cloneDeep(rows)

    // //checkDepthStartEnd
    // let ckds = checkDepthStartEnd(rows, { keyDepthStart, keyDepthEnd })
    // if (size(ckds) > 0) {
    //     throw new Error(join(ckds, ', '))
    // }

    // //sortBy
    // rows = sortBy(rows, (v) => {
    //     return cdbl(v[keyDepthStart])
    // })

    // //saveFromInds
    // if (saveFromInds) {
    //     rows = map(rows, (v, k) => {
    //         if (!isnum(v[keyInd])) {
    //             v[keyInd] = k
    //         }
    //         return v
    //     })
    // }

    // //proc
    // let proc = (rows) => {

    //     //偵測合併, 前面已檢查checkDepthStartEnd, 故各樣本起訖深度為接合
    //     let b = false
    //     for (let k = 1; k < size(rows); k++) {

    //         //v0, v1
    //         let v0 = rows[k - 1]
    //         let v1 = rows[k]

    //         //ds0, de0, vl0
    //         let ds0 = get(v0, keyDepthStart, null)
    //         // let de0 = get(v0, keyDepthEnd, null)
    //         let vl0 = get(v0, keyValue, null)

    //         //ds1, de1, vl1
    //         // let ds1 = get(v1, keyDepthStart, null)
    //         let de1 = get(v1, keyDepthEnd, null)
    //         let vl1 = get(v1, keyValue, null)

    //         //check
    //         if (vl0 === vl1) {
    //             // console.log('v0', v0, 'v1', v1)

    //             //saveFromInds
    //             if (saveFromInds) {

    //                 //check
    //                 if (!isarr(v0[keyFromInds])) {
    //                     v0[keyFromInds] = [v0[keyInd]]
    //                 }

    //                 //check
    //                 if (!isarr(v1[keyFromInds])) {
    //                     v1[keyFromInds] = [v1[keyInd]]
    //                 }

    //                 //merge
    //                 v1[keyFromInds] = [
    //                     ...v0[keyFromInds],
    //                     ...v1[keyFromInds],
    //                 ]

    //             }

    //             //vm
    //             let vm = null
    //             if (useFunMerge) {

    //                 //r
    //                 vm = funMerge(v0, v1)

    //                 //check
    //                 if (!iseobj(vm)) {
    //                     throw new Error(`funMerge回傳[${vm}]非有效物件`)
    //                 }

    //             }
    //             else {
    //                 if (typeMerge === 'up') {
    //                     vm = {
    //                         ...v1,
    //                         ...v0,
    //                     }
    //                 }
    //                 else if (typeMerge === 'down') {
    //                     vm = {
    //                         ...v0,
    //                         ...v1,
    //                     }
    //                 }
    //             }

    //             //合併深度
    //             vm[keyDepthStart] = ds0
    //             vm[keyDepthEnd] = de1
    //             // console.log('vm', vm)

    //             //儲存合併與刪除
    //             rows = cloneDeep(rows)
    //             rows[k - 1] = vm
    //             pullAt(rows, k)
    //             // console.log('rows pullAt', rows)

    //             b = true
    //             break
    //         }

    //     }

    //     return {
    //         merge: b,
    //         rows,
    //     }
    // }

    // //while proc
    // let r = proc(rows)
    // while (r.merge) {
    //     r = proc(rows)
    //     rows = r.rows
    // }

    //keyDepthStart
    let keyDepthStart = get(opt, 'keyDepthStart')
    if (!isestr(keyDepthStart)) {
        keyDepthStart = 'depthStart'
    }

    //keyDepthEnd
    let keyDepthEnd = get(opt, 'keyDepthEnd')
    if (!isestr(keyDepthEnd)) {
        keyDepthEnd = 'depthEnd'
    }

    //keyValue
    let keyValue = get(opt, 'keyValue')
    if (!isestr(keyValue)) {
        keyValue = 'value'
    }

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

    //keyInd
    let keyInd = get(opt, 'keyInd')
    if (!isestr(keyInd)) {
        keyInd = 'ind'
    }

    //keyFromInds
    let keyFromInds = get(opt, 'keyFromInds')
    if (!isestr(keyFromInds)) {
        keyFromInds = 'fromInds'
    }

    //typeDetect
    let typeDetect = get(opt, 'typeDetect')
    if (typeDetect !== 'sequence' && typeDetect !== 'iterate') {
        typeDetect = 'sequence'
    }

    //funMerge
    let funMerge = get(opt, 'funMerge')
    let useFunMerge = isfun(funMerge)

    //typeMerge
    let typeMerge = get(opt, 'typeMerge')
    if (typeMerge !== 'up' && typeMerge !== 'down') {
        typeMerge = 'down'
    }

    //cloneDeep
    rows = cloneDeep(rows)

    //checkDepthStartEnd
    let ckds = checkDepthStartEnd(rows, { keyDepthStart, keyDepthEnd })
    if (size(ckds) > 0) {
        throw new Error(join(ckds, ', '))
    }

    //sortBy
    rows = sortBy(rows, (v) => {
        return cdbl(v[keyDepthStart])
    })

    //saveFromInds
    if (saveFromInds) {
        rows = map(rows, (v, k) => {
            if (!isnum(v[keyInd])) {
                v[keyInd] = k
            }
            return v
        })
    }

    //typeDetect
    if (typeDetect === 'iterate') {

        //proc
        let proc = (rows) => {

            //偵測合併, 前面已檢查checkDepthStartEnd, 故各樣本起訖深度為接合
            let b = false
            for (let k = 1; k < size(rows); k++) {

                //v0, v1
                let v0 = rows[k - 1]
                let v1 = rows[k]

                //ds0, de0, vl0
                let ds0 = get(v0, keyDepthStart, null)
                let de0 = get(v0, keyDepthEnd, null)
                let vl0 = get(v0, keyValue, null)

                //ds1, de1, vl1
                let ds1 = get(v1, keyDepthStart, null)
                let de1 = get(v1, keyDepthEnd, null)
                let vl1 = get(v1, keyValue, null)

                //check
                if (vl0 === null) {
                    console.log('v0', v0, 'keyValue', keyValue)
                    throw new Error(`invalid vl0`)
                }
                if (vl1 === null) {
                    console.log('v1', v1, 'keyValue', keyValue)
                    throw new Error(`invalid vl1`)
                }

                //check
                if (!isnum(ds0) || !isnum(de0) || !isnum(ds1) || !isnum(de1)) {
                    return true //跳出換下一個
                }

                //cdbl
                ds0 = cdbl(ds0)
                de0 = cdbl(de0)
                ds1 = cdbl(ds1)
                de1 = cdbl(de1)

                //check
                if (vl0 === vl1 && judge(de0, '===', ds1)) {
                // console.log('v0', v0, 'v1', v1)

                    //saveFromInds
                    if (saveFromInds) {

                        //check
                        if (!isarr(v0[keyFromInds])) {
                            v0[keyFromInds] = [v0[keyInd]]
                        }

                        //check
                        if (!isarr(v1[keyFromInds])) {
                            v1[keyFromInds] = [v1[keyInd]]
                        }

                        //merge
                        v1[keyFromInds] = [
                            ...v0[keyFromInds],
                            ...v1[keyFromInds],
                        ]

                    }

                    //vm
                    let vm = null
                    if (useFunMerge) {

                        //r
                        vm = funMerge(v0, v1)

                        //check
                        if (!iseobj(vm)) {
                            throw new Error(`funMerge回傳[${vm}]非有效物件`)
                        }

                    }
                    else {
                        if (typeMerge === 'up') {
                            vm = {
                                ...v1,
                                ...v0,
                            }
                        }
                        else if (typeMerge === 'down') {
                            vm = {
                                ...v0,
                                ...v1,
                            }
                        }
                    }

                    //更新起訖深度
                    vm[keyDepthStart] = ds0
                    vm[keyDepthEnd] = de1
                    // console.log('vm', vm)

                    //儲存合併與刪除
                    rows = cloneDeep(rows)
                    rows[k - 1] = vm
                    pullAt(rows, k)
                    // console.log('rows pullAt', rows)

                    b = true
                    break
                }

            }

            return {
                merge: b,
                rows,
            }
        }

        //while proc
        let r = proc(rows)
        while (r.merge) {
            r = proc(rows)
            rows = r.rows
        }

    }
    else if (typeDetect === 'sequence') {

        //偵測各層
        each(rows, (v, k) => {

            //check
            if (k === 0) {
                return true //跳出換下一個
            }

            //check
            if (!iseobj(v)) {
                throw new Error(`rows[${k}] is not an object`)
            }

            //v0, v1
            let v0 = get(rows, k - 1)
            let v1 = v

            //ds0, de0, vl0
            let ds0 = get(v0, keyDepthStart, null)
            let de0 = get(v0, keyDepthEnd, null)
            let vl0 = get(v0, keyValue, null)

            //ds1, de1, vl1
            let ds1 = get(v1, keyDepthStart, null)
            let de1 = get(v1, keyDepthEnd, null)
            let vl1 = get(v1, keyValue, null)

            //check
            if (vl0 === null) {
                console.log('v0', v0, 'keyValue', keyValue)
                throw new Error(`invalid vl0`)
            }
            if (vl1 === null) {
                console.log('v1', v1, 'keyValue', keyValue)
                throw new Error(`invalid vl1`)
            }

            //check
            if (!isnum(ds0) || !isnum(de0) || !isnum(ds1) || !isnum(de1)) {
                return true //跳出換下一個
            }

            //cdbl
            ds0 = cdbl(ds0)
            de0 = cdbl(de0)
            ds1 = cdbl(ds1)
            de1 = cdbl(de1)

            //check
            if (vl0 === vl1 && judge(de0, '===', ds1)) {

                //saveFromInds
                if (saveFromInds) {

                    //check
                    if (!isarr(v0[keyFromInds])) {
                        v0[keyFromInds] = [v0[keyInd]]
                    }

                    //check
                    if (!isarr(v1[keyFromInds])) {
                        v1[keyFromInds] = [v1[keyInd]]
                    }

                    //merge
                    v1[keyFromInds] = [
                        ...v0[keyFromInds],
                        ...v1[keyFromInds],
                    ]

                }

                //vm
                let vm = null
                if (useFunMerge) {

                    //r
                    vm = funMerge(v0, v1) //funMerge須回傳合併層參數, 除起始深度會自動給予上層, 其他參數得自行回傳

                    //check
                    if (!iseobj(vm)) {
                        throw new Error(`funMerge回傳[${vm}]非有效物件`)
                    }

                }
                else {
                    if (typeMerge === 'up') {
                        vm = {
                            ...v1,
                            ...v0,
                        }
                    }
                    else if (typeMerge === 'down') {
                        vm = {
                            ...v0,
                            ...v1,
                        }
                    }
                }

                //合併深度
                vm[keyDepthStart] = ds0
                vm[keyDepthEnd] = de1

                //儲存數據至下層
                rows[k] = vm

                //標注上層為null(待刪除)
                rows[k - 1] = null

            }

        })

        //filter
        rows = filter(rows, iseobj)

    }

    return rows
}


export default mergeByDepthStartEnd