interp2Raster.mjs

import get from 'lodash-es/get.js'
import each from 'lodash-es/each.js'
import isNumber from 'lodash-es/isNumber.js'
import isearr from 'wsemi/src/isearr.mjs'
import isestr from 'wsemi/src/isestr.mjs'
import isnum from 'wsemi/src/isnum.mjs'
import isfun from 'wsemi/src/isfun.mjs'
import cdbl from 'wsemi/src/cdbl.mjs'
import _kriging from './interp2Kriging.mjs'
import aggregatePoints from './aggregatePoints.mjs'
import interp2Grid from './interp2Grid.mjs'


/**
 * 不規則點資料先聚合再內插至規則網格
 *
 * Unit Test: {@link https://github.com/yuda-lyu/w-gis/blob/master/test/interp2Raster.test.mjs Github}
 * @memberOf w-gis
 * @param {Array} ops 輸入二維座標加觀測數據點陣列,為[{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 {String} [opt.keyZ='z'] 輸入點物件之z欄位字串,為觀測值,預設'z'
 * @param {String} [opt.modePick='min'] 輸入挑選方式字串,可選'min'、'max',預設'min'
 * @param {Number} [opt.dx=0.000979087] 輸入網格x向間距數字,預設0.000979087,基於WGS84經緯度之數據點,代表約100m
 * @param {Number} [opt.dx=0.000906247] 輸入網格y向間距數字,預設0.000906247,基於WGS84經緯度之數據點,代表約100m
 * @param {Number} [opt.dxAgr=opt.dx*2] 輸入先聚合時網格x向間距數字,預設opt.dx*2
 * @param {Number} [opt.dyAgr=opt.dy*2] 輸入先聚合時網格y向間距數字,預設opt.dy*2
 * @param {Function} [opt.funValid=(x,y)=>{return true}] 輸入確認點座標(x,y)是否有效函數,回傳布林值,可使用Promise回傳,預設(x,y)=>{return true}
 * @param {Function} [opt.funKriging=interp2Kriging] 輸入克利金處理函數,預設使用內建interp2Kriging
 * @param {Function} [opt.funAdjust=(x,y,z)=>{return z}] 輸入內插後值調整函數,用於修正不合理值或做後處理,回傳布林值,可使用Promise回傳,預設(x,y,z)=>{return z}
 * @param {Number} [opt.scale=1] 輸入當funKriging使用interp2Kriging時,正規化範圍數值,因處理多邊形時有數值容許誤差,故須通過縮放值域來減少問題,預設1是正規化0至1之間,使用scaleXY則是正規化為0至scaleXY之間,預設1
 * @param {String} [opt.model='exponential'] 輸入當funKriging使用interp2Kriging時,擬合模式字串,可選'exponential'、'gaussian'、'spherical',預設'exponential'
 * @param {Number} [opt.sigma2=0] 輸入當funKriging使用interp2Kriging時,自動擬合參數sigma2數值,預設0
 * @param {Number} [opt.alpha=100] 輸入當funKriging使用interp2Kriging時,自動擬合參數alpha數值,預設100
 * @returns {Object} 回傳規則網格物件,物件內grds為規則網格之二維陣列
 * @example
 *
 * let ps
 * let r
 *
 * ps = [
 *     { x: 121.48571681415929, y: 25.058614130434783, z: 95 },
 *     { x: 121.48500884955752, y: 25.062228260869563, z: 146 },
 *     { x: 121.47, y: 25.08125, z: 22 },
 *     { x: 121.53598230088495, y: 25.02475543478261, z: 223 },
 *     { x: 121.54582300884955, y: 25.074592391304346, z: 39 },
 *     { x: 121.52118584070796, y: 25.057472826086958, z: 191 },
 *     { x: 121.48083185840707, y: 25.02, z: 22 },
 *     { x: 121.50653097345132, y: 25.089429347826087, z: 249 },
 *     { x: 121.55, y: 25.09, z: 86 },
 *     { x: 121.52614159292035, y: 25.067364130434783, z: 125 }
 * ]
 * r = await interp2Raster(ps, { dx: 0.000979087 * 10, dy: 0.000906247 * 5 })
 * console.log(JSON.stringify(r))
 * // => {"xnum":9,"xmin":121.47,"xmax":121.55,"dx":0.00979087,"ynum":16,"ymin":25.02,"ymax":25.09,"dy":0.004531235,"zmin":25.008503223085306,"zmax":230.692207620399,"grds":[[53.29695613753195,90.9878283681276,143.06087191384637,200.32429562431122,230.692207620399,180.40204907064341,136.1530241094394,100.59943015055649,82.14770016468049],[33.18615340480098,82.53745908262893,136.89266690219813,189.57557481421284,211.01267466303997,174.01759815374774,129.8648554713684,90.3775726907702,69.76202213840641],[30.649106928513838,78.18086957161364,129.87666573494295,175.36672144691707,192.14083300446975,165.43595632681556,123.45781065492382,80.89449160839146,55.909387880686836],[44.35784386605923,78.19215023386876,123.34728621362704,162.59334238706708,178.14944804497486,158.24273885280672,118.72593945913597,77.34481966112054,47.557053415271795],[54.59624175640077,79.97235367298671,117.80692991239255,152.5691700549194,169.3604923358124,155.84955967852204,117.47530627008295,84.12984269825354,61.541566831759006],[61.921968156117515,81.52387857586544,113.0710912563389,145.1243482533917,165.07967283545923,161.80751391298088,126.65650015693579,97.96229710366745,79.82779997828177],[66.83219014217913,82.1918706583026,108.82981182150552,139.51924993924777,163.39654235447964,175.8727178307613,144.86370463051009,114.17834904512225,97.09515848645559],[69.71941699919188,82.20986544471259,105.48343246225039,134.8766711857756,161.6209193581903,185.51610085077323,159.2203228863815,129.86565172082058,112.88018028969536],[70.82153526794654,81.40365825844987,102.52998415554532,130.44222819267918,158.20368573475835,179.40458853522884,166.87690247501067,143.74286964403262,127.15417129624454],[70.24547760586455,79.10219254059587,98.72806515388253,125.74805126341245,153.60756995565745,173.79734055412013,171.50300218848182,155.95696527327257,140.05755661594753],[68.06294143513142,74.97983328526666,93.69964174320266,120.64337719371085,148.81678189404954,170.3067562843946,176.08450938057823,167.37076517487893,151.7970112583186],[64.40679807848441,68.87644483066734,87.36596771611377,115.20448065026856,144.34102924872468,168.44082899163902,181.65959964982952,178.91839896983052,162.45918562235704],[59.579516621263224,60.65421884945618,79.85418875855228,109.67073197494572,140.27156102781217,167.4089506411862,188.16721978253224,191.29745012452534,171.7427174412858],[54.25750734739524,50.16380421262544,71.68195808846065,104.45932666464594,136.53145068212558,166.22770388291246,194.18596156854792,204.55151424952277,178.67429685300704],[49.84400365402098,37.301157966447214,64.30077984326395,100.16770574008851,133.04693099333764,163.94549799236063,195.79253736299714,213.05406176918385,181.76602702017865],[48.51705283717487,25.008503223085306,60.667024580062325,97.41331557750496,129.82668100790102,160.1190717664935,189.93474888031673,202.82202458292304,180.33605168933383]],"pts":[{"x":121.48571681415929,"y":25.058614130434783,"z":95},{"x":121.47,"y":25.08125,"z":22},{"x":121.53598230088495,"y":25.02475543478261,"z":223},{"x":121.54582300884955,"y":25.074592391304346,"z":39},{"x":121.52118584070796,"y":25.057472826086958,"z":191},{"x":121.48083185840707,"y":25.02,"z":22},{"x":121.50653097345132,"y":25.089429347826087,"z":249},{"x":121.55,"y":25.09,"z":86},{"x":121.52614159292035,"y":25.067364130434783,"z":125}]}
 *
 * ps = [
 *     { x: 121.48571681415929, y: 25.058614130434783, z: 95 },
 *     { x: 121.48500884955752, y: 25.062228260869563, z: 146 },
 *     { x: 121.47, y: 25.08125, z: 22 },
 *     { x: 121.53598230088495, y: 25.02475543478261, z: 223 },
 *     { x: 121.54582300884955, y: 25.074592391304346, z: 39 },
 *     { x: 121.52118584070796, y: 25.057472826086958, z: 191 },
 *     { x: 121.48083185840707, y: 25.02, z: 22 },
 *     { x: 121.50653097345132, y: 25.089429347826087, z: 249 },
 *     { x: 121.55, y: 25.09, z: 86 },
 *     { x: 121.52614159292035, y: 25.067364130434783, z: 125 }
 * ]
 * r = await interp2Raster(ps, { dx: 0.000979087 * 10, dy: 0.000906247 * 5, dxAgr: 0.000979087 * 15, dyAgr: 0.000906247 * 7.5 })
 * console.log(JSON.stringify(r))
 * // => {"xnum":9,"xmin":121.47,"xmax":121.55,"dx":0.00979087,"ynum":16,"ymin":25.02,"ymax":25.09,"dy":0.004531235,"zmin":24.823924446215038,"zmax":231.7567577657555,"grds":[[53.62369559375291,97.28753826038451,151.99569385250032,206.01329420379795,231.7567577657555,181.74456038323083,136.9337797009959,100.82047078083103,82.07708777678545],[33.35769875360963,92.00300714953804,149.54057920145087,198.09758546908188,214.41704248676362,175.7821324384034,130.5283656577402,90.4093497431664,69.54075119287774],[33.694057708538715,92.54101428394036,147.39251200590925,187.06516519367833,197.62513561758846,167.6668963058992,123.98735585892726,80.78686225833944,55.66889834101754],[52.64381088478393,99.03900142300567,146.65595863609053,176.99055560470293,185.00298561118308,160.73147948072238,119.0363573373561,77.12115108746592,47.37939731126598],[66.61879668531361,107.79408272120735,147.016778115003,168.30294933707412,176.7095557492657,158.26543997764273,117.45655109967052,83.80309340175263,61.27750967567075],[75.2589561237669,113.5537749334791,144.98709654332248,159.8929007644603,172.0170905880976,163.84236052111794,126.4398496041521,97.63064967940787,79.59691654868394],[78.68117509942893,106.5552658165186,128.80117256854788,150.87976018747153,169.2068860796617,177.25446108422068,144.68301313512228,113.99037990431796,97.05253698651693],[78.27805645580389,92.10986466514447,109.16559772934329,141.9067311649439,165.99935683226428,186.4085110016602,159.21170852439775,129.94467107234337,113.13480709391158],[75.97241978508316,84.57378980579321,102.53683374775467,134.05122593259682,161.25691801191817,180.48459348365037,167.21602941206322,144.15358853414438,127.76309289265961],[72.80173195680102,79.76140615655008,97.9321023375592,127.31339852980325,155.6482200843788,174.98245027782707,172.19374986330374,156.6933278368807,141.02867176590735],[68.90918098278021,74.55268398871439,92.65277069488342,121.15239269012386,150.1775616972252,171.49822261670596,177.0133654786484,168.35817943753946,153.09719732545554],[64.2076980316679,67.96041083663675,86.26929074220435,115.22108595764789,145.29251688553296,169.59539950565747,182.68489882975817,180.02516814884666,164.02895337420648],[58.78869152147344,59.58502468707952,78.83089710760079,109.49913670429187,141.00843456843626,168.5245314234247,189.15870248800343,192.34693759552368,173.52110450762643],[53.17781904277413,49.19131322151639,70.82515273215796,104.26247139846541,137.18674389853737,167.33735247437988,195.08169367782963,205.34921468759225,180.6373824848832],[48.65930356588191,36.65719928223353,63.659890609431685,100.02829363193082,133.710205844732,165.11253891467507,196.727588357127,213.69062065298513,183.9600610036589],[47.296028443616365,24.823924446215038,60.196676193427145,97.36489712827152,130.55697396278424,161.41612787019466,191.2045850563283,204.15009289540566,182.84604795685374]],"pts":[{"x":121.48571681415929,"y":25.058614130434783,"z":95},{"x":121.48500884955752,"y":25.062228260869563,"z":146},{"x":121.47,"y":25.08125,"z":22},{"x":121.53598230088495,"y":25.02475543478261,"z":223},{"x":121.54582300884955,"y":25.074592391304346,"z":39},{"x":121.52118584070796,"y":25.057472826086958,"z":191},{"x":121.48083185840707,"y":25.02,"z":22},{"x":121.50653097345132,"y":25.089429347826087,"z":249},{"x":121.55,"y":25.09,"z":86},{"x":121.52614159292035,"y":25.067364130434783,"z":125}]}
 *
 */
async function interp2Raster(ops, opt = {}) {

    let dlng = 0.000979087
    let dlat = 0.000906247

    //check
    if (!isearr(ops)) {
        throw new Error(`ops`)
    }

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

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

    //keyZ
    let keyZ = get(opt, 'keyZ')
    if (!isestr(keyZ)) {
        keyZ = 'z'
    }

    //dx
    let dx = get(opt, 'dx')
    if (!isnum(dx)) {
        dx = dlng
    }
    dx = cdbl(dx)

    //dy
    let dy = get(opt, 'dy')
    if (!isnum(dy)) {
        dy = dlat
    }
    dy = cdbl(dy)

    //dxAgr
    let dxAgr = get(opt, 'dxAgr')
    if (!isnum(dxAgr)) {
        dxAgr = dy * 2
    }
    dxAgr = cdbl(dxAgr)

    //dyAgr
    let dyAgr = get(opt, 'dyAgr')
    if (!isnum(dyAgr)) {
        dyAgr = dy * 2
    }
    dyAgr = cdbl(dyAgr)

    //modePick
    let modePick = get(opt, 'modePick')
    if (modePick !== 'min' && modePick !== 'max') {
        modePick = 'min'
    }

    //funValid
    let funValid = get(opt, 'funValid')
    if (!isfun(funValid)) {
        funValid = () => {
            return true
        }
    }

    //funKriging
    let funKriging = get(opt, 'funKriging')
    if (!isfun(funKriging)) {
        funKriging = _kriging
    }

    //funAdjust
    let funAdjust = get(opt, 'funAdjust')
    if (!isfun(funAdjust)) {
        funAdjust = (x, y, z) => {
            return z
        }
    }

    //xmin, xmax, ymin, ymax
    let xmin = 1e20
    let xmax = -1e20
    let ymin = 1e20
    let ymax = -1e20
    each(ops, (m) => {
        let v

        v = get(m, keyX)
        if (isNumber(v)) {
            xmin = Math.min(v, xmin)
            xmax = Math.max(v, xmax)
        }

        v = get(m, keyY)
        if (isNumber(v)) {
            ymin = Math.min(v, ymin)
            ymax = Math.max(v, ymax)
        }

    })
    // console.log('xmin', xmin, 'xmax', xmax)
    // console.log('ymin', ymin, 'ymax', ymax)

    //pts, 用dxAgr與dyAgr聚合提取點數據
    let pts = aggregatePoints(ops, xmin, dxAgr, ymin, dyAgr, {
        keyX,
        keyY,
        keyZ,
        modePick,
    })
    // console.log('pts', take(pts, 5), size(pts))

    //rg
    let rg = await interp2Grid(pts, xmin, xmax, dx, ymin, ymax, dy, {
        ...opt, //須提供自定義參數給funKriging使用
        funKriging,
        funValid,
        funAdjust,
        returnGrid: true,
        inverseKeyY: true,
    })
    // console.log('rg', rg)
    // console.log('keys(rg)', keys(rg))
    // keys(rg) [
    //   'xnum', 'xmin', 'xmax',
    //   'dx',   'ynum', 'ymin',
    //   'ymax', 'dy',   'zmin',
    //   'zmax', 'grds'
    // ]
    // console.log('rg.grds', rg.grds[0])
    // rg.grds [
    //   60.606247155177684,  62.92540214009434,   65.7312154911937,   68.9914947349822,
    //   72.65919624422395,  76.67923534212574,  80.99475324860614,  85.55171796733839,
    //   ...
    // ]

    //save
    rg.pts = pts

    return rg
}


export default interp2Raster