groupByDepthStartEnd.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 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 iseobj from 'wsemi/src/iseobj.mjs'
import isnum from 'wsemi/src/isnum.mjs'
import judge from './judge.mjs'
import checkDepth from './checkDepth.mjs'


/**
 * 由指定起訖深度陣列,針對各樣本之中點深度群組化,各群儲存所屬的樣本陣列
 *
 * Unit Test: {@link https://github.com/yuda-lyu/w-geo/blob/master/test/groupByDepthStartEnd.test.js Github}
 * @memberOf w-geo
 * @param {Array} rows 輸入數據陣列,各數據為物件,至少需包含深度(depth),深度單位為m
 * @param {Array|Object} depthStartAndEnds 輸入欲提取數據的起訖深度陣列或物件,為物件時需有至少需包含起始深度(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'
 * @returns {Array} 回傳群組化後添加起訖深度與所屬原數據的陣列
 * @example
 *
 * let rows
 * let ranges
 * let rs
 *
 * rows = [
 *     {
 *         depth: 0,
 *     },
 *     {
 *         depth: 1,
 *     },
 *     {
 *         depth: 3,
 *     },
 *     {
 *         depth: 10,
 *     },
 * ]
 * ranges = [
 *     {
 *         depthStart: 0,
 *         depthEnd: 1,
 *     },
 *     {
 *         depthStart: 1,
 *         depthEnd: 4,
 *     },
 * ]
 * rs = groupByDepthStartEnd(rows, ranges)
 * console.log(JSON.stringify(rs, null, 2))
 * // => [
 * //   {
 * //     "depthStart": 0,
 * //     "depthEnd": 1,
 * //     "rows": [
 * //       {
 * //         "depth": 0
 * //       },
 * //       {
 * //         "depth": 1
 * //       }
 * //     ]
 * //   },
 * //   {
 * //     "depthStart": 1,
 * //     "depthEnd": 4,
 * //     "rows": [
 * //       {
 * //         "depth": 3
 * //       }
 * //     ]
 * //   }
 * // ]
 *
 * rows = [
 *     {
 *         depth: 0,
 *     },
 *     {
 *         depth: 1,
 *     },
 *     {
 *         depth: 3,
 *     },
 *     {
 *         depth: 10,
 *     },
 * ]
 * ranges = [
 *     {
 *         depthStart: 0,
 *         depthEnd: 3,
 *     },
 *     {
 *         depthStart: 1,
 *         depthEnd: 5,
 *     },
 * ]
 * try {
 *     rs = groupByDepthStartEnd(rows, ranges)
 * }
 * catch (err) {
 *     rs = err.toString()
 * }
 * console.log(rs)
 * // => Error: 第 1 樣本結束深度depthEnd[3]大於第 2 個樣本起始深度depthStart[1]
 *
 * rows = [
 *     {
 *         depth: 0,
 *     },
 *     {
 *         depth: 1,
 *     },
 *     {
 *         depth: 3,
 *     },
 *     {
 *         depth: 10,
 *     },
 * ]
 * ranges = [
 *     {
 *         depthStart: 0,
 *         depthEnd: 2,
 *     },
 *     {
 *         depthStart: 5,
 *         depthEnd: 10,
 *     },
 * ]
 * rs = groupByDepthStartEnd(rows, ranges)
 * console.log(JSON.stringify(rs, null, 2))
 * // => [
 * //   {
 * //     "depthStart": 0,
 * //     "depthEnd": 2,
 * //     "rows": [
 * //       {
 * //         "depth": 0
 * //       },
 * //       {
 * //         "depth": 1
 * //       }
 * //     ]
 * //   },
 * //   {
 * //     "depthStart": 5,
 * //     "depthEnd": 10,
 * //     "rows": [
 * //       {
 * //         "depth": 10
 * //       }
 * //     ]
 * //   }
 * // ]
 *
 * rows = [
 *     {
 *         depth: 0,
 *     },
 *     {
 *         depth: 1,
 *     },
 *     {
 *         depth: 3,
 *     },
 *     {
 *         depth: 10,
 *     },
 * ]
 * ranges = [
 *     {
 *         depthStart: 0,
 *         depthEnd: 10,
 *     },
 * ]
 * rs = groupByDepthStartEnd(rows, ranges)
 * console.log(JSON.stringify(rs, null, 2))
 * // => [
 * //   {
 * //     "depthStart": 0,
 * //     "depthEnd": 10,
 * //     "rows": [
 * //       {
 * //         "depth": 0
 * //       },
 * //       {
 * //         "depth": 1
 * //       },
 * //       {
 * //         "depth": 3
 * //       },
 * //       {
 * //         "depth": 10
 * //       }
 * //     ]
 * //   }
 * // ]
 *
 * rows = [
 *     {
 *         dc: 0,
 *     },
 *     {
 *         dc: 1,
 *     },
 *     {
 *         dc: 3,
 *     },
 *     {
 *         dc: 10,
 *     },
 * ]
 * ranges = [
 *     {
 *         ds: 0,
 *         de: 1,
 *     },
 *     {
 *         ds: 1,
 *         de: 4,
 *     },
 * ]
 * rs = groupByDepthStartEnd(rows, ranges, { keyDepth: 'dc', keyDepthStart: 'ds', keyDepthEnd: 'de' })
 * console.log(JSON.stringify(rs, null, 2))
 * // => [
 * //   {
 * //     "ds": 0,
 * //     "de": 1,
 * //     "rows": [
 * //       {
 * //         "dc": 0
 * //       },
 * //       {
 * //         "dc": 1
 * //       }
 * //     ]
 * //   },
 * //   {
 * //     "ds": 1,
 * //     "de": 4,
 * //     "rows": [
 * //       {
 * //         "dc": 3
 * //       }
 * //     ]
 * //   }
 * // ]
 *
 */
function groupByDepthStartEnd(rows, depthStartAndEnds, opt = {}) {
    let errs = []

    //check
    if (!isearr(rows)) {
        throw new Error('無有效資料')
    }
    if (!isearr(depthStartAndEnds) && !iseobj(depthStartAndEnds)) {
        throw new Error('無有效起訖深度資料')
    }

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

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

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

    //cloneDeep
    rows = cloneDeep(rows)

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

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

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

    })

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

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

    //checkDepth
    let ckd = checkDepth(rows, { keyDepth })
    if (size(ckd) > 0) {
        throw new Error(join(ckd, ', '))
    }

    //check
    if (iseobj(depthStartAndEnds)) {
        depthStartAndEnds = [depthStartAndEnds]
    }

    //判斷depthStartAndEnds內各元素之起始深度需為有效數字
    each(depthStartAndEnds, (v, k) => {

        //ds, de
        let ds = get(v, keyDepthStart, null)
        let de = get(v, keyDepthEnd, null)

        //check
        if (!isnum(ds)) {
            errs.push(`群組用起訖深度陣列中,第 ${k} 元素之起始深度${keyDepthStart}[${ds}]非有效數字`)
        }
        if (!isnum(de)) {
            errs.push(`群組用起訖深度陣列中,第 ${k} 元素之結束深度${keyDepthEnd}[${de}]非有效數字`)
        }

    })

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

    //each
    each(depthStartAndEnds, (v, k) => {

        //ds1, de1
        let ds1 = get(v, keyDepthStart, null)
        let de1 = get(v, keyDepthEnd, null)
        ds1 = cdbl(ds1)
        de1 = cdbl(de1)

        //check
        //if (ds1 > de1) {
        if (judge(ds1, '>', de1)) {
            errs.push(`群組用起訖深度陣列中,第 ${k} 元素之起始深度${keyDepthStart}[${ds1}]大於結束深度${keyDepthEnd}[${de1}]`)
        }

        if (k === 0) {
            return true
        }

        //v0
        let v0 = get(depthStartAndEnds, k - 1)

        //de0
        let de0 = get(v0, keyDepthEnd, null)
        de0 = cdbl(de0)

        //比較「上層結束深度」是否大於「下層起始深度」, 不要求上下層連續(上層結束深度等於下層起始深度)
        //if (de0 > ds1) {
        if (judge(de0, '>', ds1)) {
            errs.push(`第 ${k} 樣本結束深度${keyDepthEnd}[${de0}]大於第 ${k + 1} 個樣本起始深度${keyDepthStart}[${ds1}]`)
        }

    })

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

    //ts, 備份, 後續會動態刪除提速
    let ts = cloneDeep(rows)

    //rs
    let rs = cloneDeep(depthStartAndEnds)
    rs = map(rs, (v, k) => {

        //ds, de
        let ds = get(v, keyDepthStart, null)
        let de = get(v, keyDepthEnd, null)
        ds = cdbl(ds)
        de = cdbl(de)

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

        //save
        v.rows = cloneDeep(rrows)

        //pullAt
        pullAt(ts, rdels)

        return v
    })

    return rs
}


export default groupByDepthStartEnd