calcDepthStartEndByGroup.mjs

import each from 'lodash-es/each.js'
import map from 'lodash-es/map.js'
import get from 'lodash-es/get.js'
import size from 'lodash-es/size.js'
import keys from 'lodash-es/keys.js'
import join from 'lodash-es/join.js'
import sortBy from 'lodash-es/sortBy.js'
import pullAt from 'lodash-es/pullAt.js'
import cloneDeep from 'lodash-es/cloneDeep.js'
import cdbl from 'wsemi/src/cdbl.mjs'
import isestr from 'wsemi/src/isestr.mjs'
import isearr from 'wsemi/src/isearr.mjs'
import isnum from 'wsemi/src/isnum.mjs'
import judge from './judge.mjs'
import calcDepthStartEndByDepth from './calcDepthStartEndByDepth.mjs'


/**
 * 由樣本指定鍵值作為群組代號,並依照樣本中點深度提取各群組之起訖深度
 *
 * Unit Test: {@link https://github.com/yuda-lyu/w-geo/blob/master/test/calcDepthStartEndByGroup.test.js Github}
 * @memberOf w-geo
 * @param {Array} rows 輸入數據陣列,各數據為物件,至少需包含起始深度(depthStart)與結束深度(depthEnd),深度單位為m
 * @param {Object} [opt={}] 輸入設定物件,預設{}
 * @param {String} [opt.keyDepth='depth'] 輸入中點深度欄位鍵值字串,預設'depth'
 * @param {String} [opt.keyGroup='group'] 輸入群組代號欄位鍵值字串,預設'group'
 * @param {String} [opt.keyDepthStart='depthStart'] 輸入欲儲存之起始深度欄位鍵值字串,預設'depthStart'
 * @param {String} [opt.keyDepthEnd='depthEnd'] 輸入欲儲存之結束深度欄位鍵值字串,預設'depthEnd'
 * @param {Number} [opt.depthEndMax=null] 輸入最大結束深度數字,若最深樣本的結束深度若小於depthEndMax,則自動給予depthEndMax,預設null
 * @returns {Array} 回傳群組化後添加起訖深度與所屬原數據的陣列
 * @example
 *
 * let rows
 * let rs
 *
 * rows = [
 *     {
 *         depth: 0,
 *         group: 'a',
 *     },
 *     {
 *         depth: 6,
 *         group: 'b',
 *     },
 *     {
 *         depth: 20,
 *         group: 'c',
 *     },
 * ]
 * rs = calcDepthStartEndByGroup(rows)
 * console.log(JSON.stringify(rs, null, 2))
 * // => [
 * //   {
 * //     "group": "a",
 * //     "depthStart": 0,
 * //     "depthEnd": 3,
 * //     "rows": [
 * //       {
 * //         "depth": 0,
 * //         "group": "a",
 * //         "depthStart": 0,
 * //         "depthEnd": 3
 * //       }
 * //     ]
 * //   },
 * //   {
 * //     "group": "b",
 * //     "depthStart": 3,
 * //     "depthEnd": 13,
 * //     "rows": [
 * //       {
 * //         "depth": 6,
 * //         "group": "b",
 * //         "depthStart": 3,
 * //         "depthEnd": 13
 * //       }
 * //     ]
 * //   },
 * //   {
 * //     "group": "c",
 * //     "depthStart": 13,
 * //     "depthEnd": 20,
 * //     "rows": [
 * //       {
 * //         "depth": 20,
 * //         "group": "c",
 * //         "depthStart": 13,
 * //         "depthEnd": 20
 * //       }
 * //     ]
 * //   }
 * // ]
 *
 * rows = [
 *     {
 *         depth: 2,
 *         group: 'a',
 *     },
 *     {
 *         depth: 6,
 *         group: 'b',
 *     },
 *     {
 *         depth: 20,
 *         group: 'c',
 *     },
 * ]
 * rs = calcDepthStartEndByGroup(rows, { depthEndMax: 25 })
 * console.log(JSON.stringify(rs, null, 2))
 * // => [
 * //   {
 * //     "group": "a",
 * //     "depthStart": 0,
 * //     "depthEnd": 4,
 * //     "rows": [
 * //       {
 * //         "depth": 2,
 * //         "group": "a",
 * //         "depthStart": 0,
 * //         "depthEnd": 4
 * //       }
 * //     ]
 * //   },
 * //   {
 * //     "group": "b",
 * //     "depthStart": 4,
 * //     "depthEnd": 13,
 * //     "rows": [
 * //       {
 * //         "depth": 6,
 * //         "group": "b",
 * //         "depthStart": 4,
 * //         "depthEnd": 13
 * //       }
 * //     ]
 * //   },
 * //   {
 * //     "group": "c",
 * //     "depthStart": 13,
 * //     "depthEnd": 25,
 * //     "rows": [
 * //       {
 * //         "depth": 20,
 * //         "group": "c",
 * //         "depthStart": 13,
 * //         "depthEnd": 25
 * //       }
 * //     ]
 * //   }
 * // ]
 *
 * rows = [
 *     {
 *         depth: 0,
 *         group: 'a',
 *     },
 *     {
 *         depth: 2,
 *         group: 'a',
 *     },
 *     {
 *         depth: 4,
 *         group: 'b',
 *     },
 *     {
 *         depth: 6,
 *         group: 'b',
 *     },
 *     {
 *         depth: 8,
 *         group: 'b',
 *     },
 *     {
 *         depth: 10,
 *         group: 'c',
 *     },
 *     {
 *         depth: 12,
 *         group: 'b',
 *     },
 *     {
 *         depth: 14,
 *         group: 'c',
 *     },
 *     {
 *         depth: 16,
 *         group: 'b',
 *     },
 *     {
 *         depth: 18,
 *         group: 'b',
 *     },
 *     {
 *         depth: 30,
 *         group: 'd',
 *     },
 * ]
 * rs = calcDepthStartEndByGroup(rows)
 * console.log(JSON.stringify(rs, null, 2))
 * // => [
 * //   {
 * //     "group": "a",
 * //     "depthStart": 0,
 * //     "depthEnd": 3,
 * //     "rows": [
 * //       {
 * //         "depth": 0,
 * //         "group": "a",
 * //         "depthStart": 0,
 * //         "depthEnd": 1
 * //       },
 * //       {
 * //         "depth": 2,
 * //         "group": "a",
 * //         "depthStart": 1,
 * //         "depthEnd": 3
 * //       }
 * //     ]
 * //   },
 * //   {
 * //     "group": "b",
 * //     "depthStart": 3,
 * //     "depthEnd": 9,
 * //     "rows": [
 * //       {
 * //         "depth": 4,
 * //         "group": "b",
 * //         "depthStart": 3,
 * //         "depthEnd": 5
 * //       },
 * //       {
 * //         "depth": 6,
 * //         "group": "b",
 * //         "depthStart": 5,
 * //         "depthEnd": 7
 * //       },
 * //       {
 * //         "depth": 8,
 * //         "group": "b",
 * //         "depthStart": 7,
 * //         "depthEnd": 9
 * //       }
 * //     ]
 * //   },
 * //   {
 * //     "group": "c",
 * //     "depthStart": 9,
 * //     "depthEnd": 11,
 * //     "rows": [
 * //       {
 * //         "depth": 10,
 * //         "group": "c",
 * //         "depthStart": 9,
 * //         "depthEnd": 11
 * //       }
 * //     ]
 * //   },
 * //   {
 * //     "group": "b",
 * //     "depthStart": 11,
 * //     "depthEnd": 13,
 * //     "rows": [
 * //       {
 * //         "depth": 12,
 * //         "group": "b",
 * //         "depthStart": 11,
 * //         "depthEnd": 13
 * //       }
 * //     ]
 * //   },
 * //   {
 * //     "group": "c",
 * //     "depthStart": 13,
 * //     "depthEnd": 15,
 * //     "rows": [
 * //       {
 * //         "depth": 14,
 * //         "group": "c",
 * //         "depthStart": 13,
 * //         "depthEnd": 15
 * //       }
 * //     ]
 * //   },
 * //   {
 * //     "group": "b",
 * //     "depthStart": 15,
 * //     "depthEnd": 24,
 * //     "rows": [
 * //       {
 * //         "depth": 16,
 * //         "group": "b",
 * //         "depthStart": 15,
 * //         "depthEnd": 17
 * //       },
 * //       {
 * //         "depth": 18,
 * //         "group": "b",
 * //         "depthStart": 17,
 * //         "depthEnd": 24
 * //       }
 * //     ]
 * //   },
 * //   {
 * //     "group": "d",
 * //     "depthStart": 24,
 * //     "depthEnd": 30,
 * //     "rows": [
 * //       {
 * //         "depth": 30,
 * //         "group": "d",
 * //         "depthStart": 24,
 * //         "depthEnd": 30
 * //       }
 * //     ]
 * //   }
 * // ]
 *
 */
function calcDepthStartEndByGroup(rows, opt = {}) {
    let errs = []

    //check
    if (!isearr(rows)) {
        throw new Error('無有效資料')
    }

    //keyDepth
    let keyDepth = get(opt, 'keyDepth')
    if (!isestr(keyDepth)) {
        keyDepth = 'depth'
    }

    //keyGroup
    let keyGroup = get(opt, 'keyGroup')
    if (!isestr(keyGroup)) {
        keyGroup = 'group'
    }

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

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

    //depthEndMax
    let depthEndMax = get(opt, 'depthEndMax')
    if (isnum(depthEndMax)) {
        depthEndMax = cdbl(depthEndMax)
    }
    else {
        depthEndMax = null
    }

    //cloneDeep
    rows = cloneDeep(rows)

    //判斷中點深度需為有效數字
    each(rows, (v, k) => {

        //dc
        let dc = get(v, keyDepth, null)

        //check
        if (!isnum(dc)) {
            errs.push(`第 ${k} 樣本中點深度${keyDepth}[${dc}]非有效數字`)
        }

        //g
        let g = get(v, keyGroup, '')

        //check
        if (!isestr(g) && !isnum(g)) {
            errs.push(`第 ${k} 樣本群組代號${keyGroup}[${g}]非有效字串或數字`)
        }

    })

    //check
    if (size(errs) > 0) {
        throw new Error(join(errs, '; '))
    }

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

    //check
    each(rows, (v, k) => {
        if (k === 0) {
            return true
        }

        //dc0, dc1
        let dc0 = get(rows, `${k - 1}.${keyDepth}`, null)
        let dc1 = get(v, keyDepth, null)
        dc0 = cdbl(dc0)
        dc1 = cdbl(dc1)

        //check
        if (judge(dc0, '>=', dc1)) {
            errs.push(`第 ${k - 1} 樣本之中點深度${keyDepth}[${dc0}]大於等於第 ${k} 樣本之中點深度${keyDepth}[${dc1}]`)
        }

    })

    //check
    if (size(errs) > 0) {
        throw new Error(join(errs, '; '))
    }

    //ks,ng
    let kg = {}
    each(rows, (v) => {
        let k = v[keyGroup]
        kg[k] = true
    })
    let ks = keys(kg) //全部群組代號陣列
    let ng = size(ks) //群數

    //check
    if (ng === 0) {
        throw new Error('無有效群數')
    }

    //calcDepthStartEndByDepth
    rows = calcDepthStartEndByDepth(rows, { keyDepth, keyDepthStart, keyDepthEnd, depthEndMax })

    //ts, 備份
    let ts = cloneDeep(rows)

    //merge
    let k = -1
    while (true) {
        k++

        //check
        if (k >= size(rows) - 1) { //遇到最後1層就跳出
            break
        }

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

        //g0, g1, g2
        // let g0 = get(v0, keyGroup, null)
        let g1 = get(v1, keyGroup, 'g1')
        let g2 = get(v2, keyGroup, 'g2')

        //detect
        if (g1 === g2) {
            rows[k][keyDepthEnd] = rows[k + 1][keyDepthEnd] //下層深度儲存至本層
            pullAt(rows, k + 1) //刪除下層
            k--
        }

    }

    //pure
    let rs = map(rows, (v) => {
        return {
            [keyGroup]: v[keyGroup],
            [keyDepthStart]: v[keyDepthStart],
            [keyDepthEnd]: v[keyDepthEnd],
        }
    })

    //add rows
    rs = map(rs, (v) => {
        let ds = v[keyDepthStart]
        let de = v[keyDepthEnd]

        let rdels = [] //本群(輪)可刪除的ts指標
        let rrows = [] //本群(輪)所屬的ts樣本
        each(ts, (t, kt) => {
            let rdc = t[keyDepth]
            if (judge(rdc, '>=', ds) && judge(rdc, '<=', de)) {
                rdels.push(kt)
                rrows.push(t)
            }
            if (judge(rdc, '>', de)) {
                return false //break
            }
        })

        //save
        v.rows = cloneDeep(rrows)

        //pullAt
        pullAt(ts, rdels)

        return v
    })

    return rs
}


export default calcDepthStartEndByGroup