import get from 'lodash-es/get.js'
import set from 'lodash-es/set.js'
import each from 'lodash-es/each.js'
import size from 'lodash-es/size.js'
import isNumber from 'lodash-es/isNumber.js'
import cloneDeep from 'lodash-es/cloneDeep.js'
import cdbl from 'wsemi/src/cdbl.mjs'
import isnum from 'wsemi/src/isnum.mjs'
import isestr from 'wsemi/src/isestr.mjs'
import isearr from 'wsemi/src/isearr.mjs'
function gv(rows, key, k) {
let v = get(rows, `${k}.${key}`)
if (isnum(v)) {
v = cdbl(v)
}
else {
v = null
}
return v
}
function findJump(rows, keyDepth, keyValue) {
let jps = []
each(rows, (dt, k) => {
if (k === 0) {
return true //跳出換下一個
}
let d0 = gv(rows, keyDepth, k - 1) //'depth(m)'
let v0 = gv(rows, keyValue, k - 1)
let v1 = gv(rows, keyValue, k)
if (isnum(v0) && !isnum(v1)) {
jps.push({
k: k - 1,
depth: d0,
value: v0,
})
}
})
return jps
}
function getSuitableBefore(rows, keyDepth, keyValue, indJump, searchLen, closedValue, opt = {}) {
return getSuitable(rows, keyDepth, keyValue, indJump, searchLen, closedValue, { dir: 'before', ...opt })
}
function getSuitableAfter(rows, keyDepth, keyValue, indJump, searchLen, closedValue, opt = {}) {
return getSuitable(rows, keyDepth, keyValue, indJump, searchLen, closedValue, { dir: 'after', ...opt })
}
function getSuitable(rows, keyDepth, keyValue, indJump, searchLen, closedValue, opt = {}) {
//dir
let dir = get(opt, 'dir')
if (dir !== 'before' && dir !== 'after') {
dir = 'after'
}
//searchMode
let searchMode = get(opt, 'searchMode')
if (searchMode !== 'all' && searchMode !== 'no' && searchMode !== 'peak' && searchMode !== 'half-closed-value' && searchMode !== 'double-closed-value' && searchMode !== 'closed-value') {
searchMode = 'all'
}
// console.log('getSuitableAfter searchMode', searchMode)
//check rows
if (size(rows) === 0) {
return null
}
//no search
if (searchMode === 'no') {
return {
k: indJump,
depth: get(rows, `${indJump}.${keyDepth}`),
value: get(rows, `${indJump}.${keyValue}`),
}
}
//check closedValue
if (!isnum(closedValue)) {
return {
k: indJump,
depth: get(rows, `${indJump}.${keyDepth}`),
value: get(rows, `${indJump}.${keyValue}`),
}
}
//d1
let d1 = gv(rows, keyDepth, indJump)
//tps
let tps = []
if (dir === 'after') {
for (let k = indJump + 1; k < size(rows) - 1; k++) {
let d2 = gv(rows, keyDepth, k)
let v2 = gv(rows, keyValue, k)
let outRange = Math.abs(d2 - d1) > searchLen
tps.push({
k,
depth: d2,
value: v2,
outRange,
})
}
}
else {
for (let k = indJump - 1; k > 0; k--) {
let d0 = gv(rows, keyDepth, k)
let v0 = gv(rows, keyValue, k)
let outRange = Math.abs(d0 - d1) > searchLen
tps.push({
k,
depth: d0,
value: v0,
outRange,
})
}
}
//ps
let ps = []
for (let k = 0; k < tps.length; k++) {
let tp = tps[k]
if (isNumber(tp.depth)) {
if (tp.outRange && size(ps) > 0) { //至少有找到1個點才可跳出, 否則超過searchLen繼續找
break
}
if (isNumber(tp.value)) {
ps.push(tp)
}
}
}
// console.log('ps', ps)
//check
if (size(ps) === 0) {
return null
}
//往下找第一個peak
if (searchMode === 'all' || searchMode === 'peak') {
for (let i = 1; i < ps.length - 1; i++) {
let p0 = ps[i - 1]
let p1 = ps[i + 0]
let p2 = ps[i + 1]
let r = 1
if (closedValue !== 0) { //closedValue非0才計算r, 否則就使用1直接視為在有效值域內
r = p1.value / closedValue
}
if (p1.value >= p0.value && p1.value >= p2.value && p0.value !== p2.value && r > 0.5 && r < 2) { //尋覓值至少要大於0.5*closedValue與小於2*closedValue
return {
k: p1.k,
depth: p1.depth,
value: p1.value,
}
}
}
}
//往下找在range內找最靠近closedValue的點
if (searchMode === 'all' || searchMode === 'closed-value') {
let diff = 1e20
let vbest = null
for (let i = 0; i < ps.length; i++) {
let p1 = ps[i + 0]
if (p1.outRange) {
break //跳出迴圈, 已超過range
}
let difft = Math.abs(p1.value - closedValue) //尋覓值最接近closedValue/2
if (diff > difft) {
diff = difft
vbest = {
k: p1.k,
depth: p1.depth,
value: p1.value,
}
}
}
if (vbest !== null) {
return vbest
}
}
//往下找最靠近closedValue/2的點
let vbestLow = null
if (searchMode === 'all' || searchMode === 'half-closed-value') {
let diff = 1e20
let vbest = null
for (let i = 0; i < ps.length; i++) {
let p1 = ps[i + 0]
let difft = Math.abs(p1.value - closedValue / 2) //尋覓值最接近closedValue/2
if (diff > difft) {
diff = difft
vbest = {
diff,
k: p1.k,
depth: p1.depth,
value: p1.value,
}
}
}
if (vbest !== null) {
vbestLow = vbest
}
}
//往下找最靠近closedValue*1.5的點
let vbestUp = null
if (searchMode === 'all' || searchMode === 'double-closed-value') {
let diff = 1e20
let vbest = null
for (let i = 0; i < ps.length; i++) {
let p1 = ps[i + 0]
let difft = Math.abs(p1.value - closedValue * 1.5) //尋覓值最接近closedValue*1.5
if (diff > difft) {
diff = difft
vbest = {
diff,
k: p1.k,
depth: p1.depth,
value: p1.value,
}
}
}
if (vbest !== null) {
vbestUp = vbest
}
}
//check
if (vbestLow !== null && vbestUp === null) {
delete vbestLow.diff
return vbestLow
}
else if (vbestLow === null && vbestUp !== null) {
delete vbestUp.diff
return vbestUp
}
else if (vbestLow === null && vbestUp === null) {
//比對vbestLow(closedValue/2)與vbestUp(closedValue*1.5), 選差值最小的回傳
let vbest = null
if (vbestLow.diff <= vbestUp.diff) {
vbest = vbestLow
}
else {
vbest = vbestUp
}
delete vbest.diff
return vbest
}
return null
}
function cmpLinearFill(rows, keys, k0, k1) {
//check
if (k0 === k1) {
return
}
else if (k0 > k1) {
throw new Error('k0 > k1')
}
//cloneDeep
let rowsTemp = cloneDeep(rows)
//keys
each(keys, (key) => {
//v0, v1
let v0 = gv(rows, key, k0)
let v1 = gv(rows, key, k1)
let len = k1 - k0
//set
for (let k = k0; k <= k1; k++) {
let w0 = (k - k0) / len
let w1 = (k1 - k) / len
let v = w1 * v0 + w0 * v1
// console.log('set', key, 'w0, w1', w0, w1, 'k, v', k, v, 'v0', v0, 'v1', v1)
set(rowsTemp, `${k}.${key}`, v)
}
})
return rowsTemp
}
function cmpCut(rows, keys, k0, k1) {
//check
if (k0 === k1) {
return
}
else if (k0 > k1) {
throw new Error('k0 > k1')
}
//cloneDeep
let rowsTemp = cloneDeep(rows)
//keys
each(keys, (key) => {
//set
for (let k = k0; k <= k1; k++) {
set(rowsTemp, `${k}.${key}`, '')
}
})
return rowsTemp
}
/**
* 數據急墜段偵測與處理
*
* Unit Test: {@link https://github.com/yuda-lyu/w-geo/blob/master/test/complementDepthData.test.js Github}
* @memberOf w-geo
* @param {Array} rows 輸入數據陣列,各數據為物件,至少需包含深度(depth,單位m)與任意數值
* @param {String} keyDepth 輸入深度欄位字串
* @param {String} keyValue 輸入偵測數值欄位字串
* @param {Array} keysValueCmp 輸入偵測數值與連帶處理欄位陣列
* @param {Object} [opt={}] 輸入設定物件,預設{}
* @param {Number} [opt.searchLen=0.5] 輸入偵測有效深度範圍數字,單位m,預設0.5
* @param {String} [opt.beforeSearchMode='no'] 輸入偵測急墜段時之往上提取起始點方式字串,可選'all'、'no'、'peak'、'half-closed-value'、'double-closed-value'、'closed-value',預設'no'
* @param {String} [opt.afterSearchMode='all'] 輸入偵測急墜段時之往下提取結束點方式字串,可選'all'、'no'、'peak'、'half-closed-value'、'double-closed-value'、'closed-value',預設'all'
* @param {String} [opt.cmpMode='linear-fill'] 輸入偵測急墜段並標註起訖點時之處理方式字串,可選'linear-fill'、'cut',預設'linear-fill'
* @returns {Array} 回傳計算後數據陣列
* @example
*
* let rows = [
* {
* 'depth(m)': 0,
* 'qc(MPa)': 0,
* 'fs(MPa)': 0,
* 'u2(MPa)': 0,
* },
* {
* 'depth(m)': 0.1,
* 'qc(MPa)': 0.12,
* 'fs(MPa)': 0.02,
* 'u2(MPa)': 0.05,
* },
* ]
*
* let keyDepth = 'depth(m)'
* let keyValue = 'qc(MPa)'
* let keysValueCmp = ['qc(MPa)', 'fs(MPa)', 'u2(MPa)']
*
* let rsFill = complementDepthData(rows, keyDepth, keyValue, keysValueCmp, { cmpMode: 'linear-fill' })
*
* let rsCut = complementDepthData(rows, keyDepth, keyValue, keysValueCmp, { cmpMode: 'cut' })
*
*/
function complementDepthData(rows, keyDepth, keyValue, keysValueCmp, opt = {}) {
//check
if (!isestr(keyDepth)) {
throw new Error('invalid keyDepth')
}
if (!isestr(keyValue)) {
throw new Error('invalid keyValue')
}
if (!isearr(keysValueCmp)) {
throw new Error('invalid keysValueCmp')
}
//cloneDeep
rows = cloneDeep(rows)
//searchLen
let searchLen = get(opt, 'searchLen')
if (!isnum(searchLen)) {
searchLen = 0.5
}
//beforeSearchMode
let beforeSearchMode = get(opt, 'beforeSearchMode')
if (beforeSearchMode !== 'all' && beforeSearchMode !== 'no' && beforeSearchMode !== 'peak' && beforeSearchMode !== 'half-closed-value' && beforeSearchMode !== 'double-closed-value' && beforeSearchMode !== 'closed-value') {
beforeSearchMode = 'no'
}
//afterSearchMode
let afterSearchMode = get(opt, 'afterSearchMode')
if (afterSearchMode !== 'all' && afterSearchMode !== 'no' && afterSearchMode !== 'peak' && afterSearchMode !== 'half-closed-value' && afterSearchMode !== 'double-closed-value' && afterSearchMode !== 'closed-value') {
afterSearchMode = 'all'
}
//cmpMode
let cmpMode = get(opt, 'cmpMode')
if (cmpMode !== 'linear-fill' && cmpMode !== 'cut') {
cmpMode = 'linear-fill'
}
//findJump
let jps = findJump(rows, keyDepth, keyValue)
// console.log('jps', jps)
each(jps, (jp) => {
// console.log('jp', jp)
//getSuitableBefore
let pbef = null
pbef = getSuitableBefore(rows, keyDepth, keyValue, jp.k, searchLen, get(jp, 'value'), { searchMode: beforeSearchMode })
//check
if (pbef === null) {
return true //跳出換下一個
}
// console.log('pbef', pbef)
//getSuitableAfter
let paft = null
paft = getSuitableAfter(rows, keyDepth, keyValue, jp.k, searchLen, get(pbef, 'value') || get(jp, 'value'), { searchMode: afterSearchMode })
//check
if (paft === null) {
return true //跳出換下一個
}
// console.log('paft', paft)
//cmpLinearFill
if (cmpMode === 'linear-fill') {
rows = cmpLinearFill(rows, keysValueCmp, pbef.k, paft.k)
}
else if (cmpMode === 'cut') {
rows = cmpCut(rows, keysValueCmp, pbef.k, paft.k)
}
})
return rows
}
export default complementDepthData