import get from 'lodash-es/get.js'
import each from 'lodash-es/each.js'
import map from 'lodash-es/map.js'
import sum from 'lodash-es/sum.js'
import size from 'lodash-es/size.js'
import isNumber from 'lodash-es/isNumber.js'
import isestr from 'wsemi/src/isestr.mjs'
import iseobj from 'wsemi/src/iseobj.mjs'
import isearr from 'wsemi/src/isearr.mjs'
import isnum from 'wsemi/src/isnum.mjs'
import isfun from 'wsemi/src/isfun.mjs'
import cdbl from 'wsemi/src/cdbl.mjs'
import haskey from 'wsemi/src/haskey.mjs'
import ptsXYtoArr from './ptsXYtoArr.mjs'
import ptsXYZtoArr from './ptsXYZtoArr.mjs'
import interp2Normalize from './interp2Normalize.mjs'
import getAreaPolygon from './getAreaPolygon.mjs'
import intersectPolygon from './intersectPolygon.mjs'
import { Delaunay } from 'd3-delaunay'
// import Delaunator from 'delaunator'
// function isPointInPolygon(polygon, point) {
// let n = polygon.length
// let p = polygon[n - 1]
// let x = point[0]
// let y = point[1]
// let x0 = p[0]; let y0 = p[1]
// let x1
// let y1
// let inside = false
// for (let i = 0; i < n; ++i) {
// p = polygon[i]
// x1 = p[0]
// y1 = p[1]
// if (((y1 > y) !== (y0 > y)) && (x < (x0 - x1) * (y - y1) / (y0 - y1) + x1)) {
// inside = !inside
// }
// x0 = x1
// y0 = y1
// }
// return inside
// }
/**
* 自然鄰點內插法(Natural Neighbor Interpolation)內插點數值
*
* A Fast and Accurate Algorithm for Natural Neighbor Interpolation
* https://gwlucastrig.github.io/TinfourDocs/NaturalNeighborTinfourAlgorithm/index.html
*
* An Introduction to Natural Neighbor Interpolation
* https://gwlucastrig.github.io/TinfourDocs/NaturalNeighborIntro/index.html
*
* [wiki]Natural neighbor interpolation
* https://en.wikipedia.org/wiki/Natural_neighbor_interpolation
*
* Natural neighbor interpolation
* https://observablehq.com/@karlerss/natural-neighbor-interpolation
*
* d3-delaunay
* https://github.com/d3/d3-delaunay
*
* Delaunator
* https://github.com/mapbox/delaunator
*
* Unit Test: {@link https://github.com/yuda-lyu/w-gis/blob/master/test/interp2NaturalNeighbor.test.mjs Github}
* @memberOf w-gis
* @param {Array} psSrc 輸入二維座標加觀測數據點陣列,為[{x:x1,y:y1,z:z1},{x:x2,y:y2,z:z2},...]點物件之陣列
* @param {Array|Object} psTar 輸入二維座標點陣列或點物件,為[{x:x1,y:y1},{x:x2,y:y2},...]點物件之陣列,或{x:x1,y:y1}點物件
* @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 {Number} [opt.scale=1] 輸入正規化範圍數值,因polybooljs處理多邊形時有數值容許誤差,故須通過縮放值域來減少問題,預設1是正規化0至1之間,使用scaleXY則是正規化為0至scaleXY之間,預設1
* @returns {Array|Object} 回傳點物件陣列或點物件,若發生錯誤則回傳錯誤訊息物件
* @example
*
* let ps
* let p
* let r
*
* ps = [{ x: 243, y: 206, z: 95 }, { x: 233, y: 225, z: 146 }, { x: 21, y: 325, z: 22 }, { x: 953, y: 28, z: 223 }, { x: 1092, y: 290, z: 39 }, { x: 744, y: 200, z: 191 }, { x: 174, y: 3, z: 22 }, { x: 537, y: 368, z: 249 }, { x: 1151, y: 371, z: 86 }, { x: 814, y: 252, z: 125 }]
* p = {
* x: 243,
* y: 207,
* }
* r = interp2NaturalNeighbor(ps, p)
* console.log(r)
* // => { x: 243, y: 207, z: 97.29447682486813 }
*
* ps = [{ x: 243, y: 206, z: 95 }, { x: 233, y: 225, z: 146 }, { x: 21, y: 325, z: 22 }, { x: 953, y: 28, z: 223 }, { x: 1092, y: 290, z: 39 }, { x: 744, y: 200, z: 191 }, { x: 174, y: 3, z: 22 }, { x: 537, y: 368, z: 249 }, { x: 1151, y: 371, z: 86 }, { x: 814, y: 252, z: 125 }]
* p = {
* x: 1243,
* y: 1207,
* }
* r = interp2NaturalNeighbor(ps, p)
* console.log(r)
* // => { x: 1243, y: 1207, z: null }
*
* ps = [{ x: 243, y: 206, z: 95 }, { x: null, y: 201, z: 122 }, { x: 233, y: 225, z: 146 }, { x: 21, y: 325, z: 22 }, { x: 953, y: 28, z: 223 }, { x: 1092, y: 290, z: 39 }, { x: 744, y: 200, z: 191 }, { x: 174, y: 3, z: 22 }, { x: 537, y: 368, z: 249 }, { x: 1151, y: 371, z: 86 }, { x: 814, y: 252, z: 125 }]
* p = {
* x: 243,
* y: 207,
* }
* let funInterpFragment = (msg) => {
* // console.log('funInterpFragment', msg)
* return msg.v //預設回傳msg.v, 三角形三角點各點v為rA*z, 故三點之rA合為1, 指定內插值z為三角點v之總和(v1,v2,v3)
* }
* r = interp2NaturalNeighbor(ps, p, { funInterpFragment })
* console.log(r)
* // => { x: 243, y: 207, z: 97.29447682486813 }
*
* ps = [{ x: 243, y: 206, z: 95 }, { x: null, y: 201, z: 122 }, { x: 233, y: 225, z: 146 }, { x: 21, y: 325, z: 22 }, { x: 953, y: 28, z: 223 }, { x: 1092, y: 290, z: 39 }, { x: 744, y: 200, z: 191 }, { x: 174, y: 3, z: 22 }, { x: 537, y: 368, z: 249 }, { x: 1151, y: 371, z: 86 }, { x: 814, y: 252, z: 125 }]
* p = {
* x: 1243,
* y: 1207,
* }
* let funInterpFragmentNoUse = (msg) => {
* // console.log('funInterpFragmentNoUse', msg)
* return msg.v //預設回傳msg.v, 此處因內插點於原始點所形成最小凸多邊形之外, 故無法內插, 亦不會呼叫funInterpFragment
* }
* r = interp2NaturalNeighbor(ps, p, { funInterpFragment: funInterpFragmentNoUse })
* console.log(r)
* // => { x: 1243, y: 1207, z: null }
*
* ps = [{ x: 243, y: 206, z: 95 }, { x: null, y: 201, z: 122 }, { x: 233, y: 225, z: 146 }, { x: 21, y: 325, z: 22 }, { x: 953, y: 28, z: 223 }, { x: 1092, y: 290, z: 39 }, { x: 744, y: 200, z: 191 }, { x: 174, y: 3, z: 22 }, { x: 537, y: 368, z: 249 }, { x: 1151, y: 371, z: 86 }, { x: 814, y: 252, z: 125 }]
* p = {
* x: 243,
* y: 207,
* }
* let funInterpFragments = (msg) => {
* // console.log('funInterpFragments', msg)
* // let v = ps[0].v + ps[1].v + ps[2].v
* return msg.v //預設回傳msg.v, 三角形三角點為msg.ps, 各點v為rA*z, 故三點之rA合為1, 指定內插值z為三角點v之總和(v1,v2,v3)
* }
* r = interp2NaturalNeighbor(ps, p, { funInterpFragments })
* console.log(r)
* // => { x: 243, y: 207, z: 97.29447682486813 }
*
* ps = [{ x: 243, y: 206, z: 95 }, { x: 233, y: 225, z: 146 }, { x: 21, y: 325, z: 22 }, { x: 953, y: 28, z: 223 }, { x: 1092, y: 290, z: 39 }, { x: 744, y: 200, z: 191 }, { x: 174, y: 3, z: 22 }, { x: 537, y: 368, z: 249 }, { x: 1151, y: 371, z: 86 }, { x: 814, y: 252, z: 125 }]
* p = {
* x: 283,
* y: 207,
* }
* r = interp2NaturalNeighbor(ps, p)
* console.log(r)
* // => { x: 283, y: 207, z: 114.43040421951906 }
*
* ps = [{ x: 243, y: 206, z: 95 }, { x: 233, y: 225, z: 146 }, { x: 21, y: 325, z: 22 }, { x: 953, y: 28, z: 223 }, { x: 1092, y: 290, z: 39 }, { x: 744, y: 200, z: 191 }, { x: 174, y: 3, z: 22 }, { x: 537, y: 368, z: 249 }, { x: 1151, y: 371, z: 86 }, { x: 814, y: 252, z: 125 }]
* p = {
* x: 1160,
* y: 380,
* }
* r = interp2NaturalNeighbor(ps, p)
* console.log(r)
* // => { x: 1160, y: 380, z: null }
*
* ps = [{ a: 243, b: 206, c: 95 }, { a: 233, b: 225, c: 146 }, { a: 21, b: 325, c: 22 }, { a: 953, b: 28, c: 223 }, { a: 1092, b: 290, c: 39 }, { a: 744, b: 200, c: 191 }, { a: 174, b: 3, c: 22 }, { a: 537, b: 368, c: 249 }, { a: 1151, b: 371, c: 86 }, { a: 814, b: 252, c: 125 }]
* p = {
* a: 243,
* b: 207,
* }
* r = interp2NaturalNeighbor(ps, p, { keyX: 'a', keyY: 'b', keyZ: 'c' })
* console.log(r)
* // => { a: 243, b: 207, c: 97.29447682486813 }
*
* ps = [{ x: 243, y: 206, z: 95 }, { x: 233, y: 225, z: 146 }, { x: 21, y: 325, z: 22 }, { x: 953, y: 28, z: 223 }, { x: 1092, y: 290, z: 39 }, { x: 744, y: 200, z: 191 }, { x: 174, y: 3, z: 22 }, { x: 537, y: 368, z: 249 }, { x: 1151, y: 371, z: 86 }, { x: 814, y: 252, z: 125 }]
* p = [
* {
* x: 243,
* y: 207,
* },
* {
* x: 283,
* y: 207,
* },
* ]
* r = interp2NaturalNeighbor(ps, p)
* console.log(r)
* // => [
* // { x: 243, y: 207, z: 97.29447682486813 },
* // { x: 283, y: 207, z: 114.43040421951906 }
* // ]
*
* ps = [{ x: 0.000243, y: 0.000206, z: 95 }, { x: 0.000233, y: 0.000225, z: 146 }, { x: 0.000021, y: 0.000325, z: 22 }, { x: 0.000953, y: 0.000028, z: 223 }, { x: 0.001092, y: 0.00029, z: 39 }, { x: 0.000744, y: 0.000200, z: 191 }, { x: 0.000174, y: 0.000003, z: 22 }, { x: 0.000537, y: 0.000368, z: 249 }, { x: 0.001151, y: 0.000371, z: 86 }, { x: 0.000814, y: 0.000252, z: 125 }]
* p = {
* x: 0.000243,
* y: 0.000207,
* }
* r = interp2NaturalNeighbor(ps, p)
* console.log(r)
* // => { x: 0.000243, y: 0.000207, z: 97.2944768248678 }
*
* ps = [{ x: 243, y: 206, z: 95 }, { x: 233, y: 225, z: 146 }, { x: 21, y: 325, z: 22 }, { x: 953, y: 28, z: 223 }, { x: 1092, y: 290, z: 39 }, { x: 744, y: 200, z: 191 }, { x: 174, y: 3, z: 22 }, { x: 537, y: 368, z: 249 }, { x: 1151, y: 371, z: 86 }, { x: 814, y: 252, z: 125 }]
* p = {
* x: 243,
* y: 207,
* }
* r = interp2NaturalNeighbor(ps, p, { scale: 1000 })
* console.log(r)
* // => { x: 243, y: 207, z: 97.29447682486855 }
*
*/
function interp2NaturalNeighbor(psSrc, psTar, opt = {}) {
//check psSrc
if (!isearr(psSrc)) {
return {
err: 'psSrc is not an array'
}
}
//check psTar
if (!iseobj(psTar) && !isearr(psTar)) {
return {
err: 'psTar is not an object or array'
}
}
//isOne
let isOne = iseobj(psTar)
if (isOne) {
psTar = [psTar]
}
//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'
}
//scale
let scale = get(opt, 'scale')
if (!isnum(scale)) {
scale = 1
}
scale = cdbl(scale)
//funInterpFragment
let funInterpFragment = get(opt, 'funInterpFragment')
let useFunInterpFragment = isfun(funInterpFragment)
//funInterpFragments
let funInterpFragments = get(opt, 'funInterpFragments')
let useFunInterpFragments = isfun(funInterpFragments)
//keyInd
let keyInd = 'ind'
//ptsXYZtoArr
psSrc = ptsXYZtoArr(psSrc, { keyX, keyY, keyZ, keyInd })
//check psSrc
if (size(psSrc) === 0) {
return {
err: 'psSrc has no effective data'
}
}
// console.log('ptsXYZtoArr psSrc', psSrc)
//ptsXYtoArr
psTar = ptsXYtoArr(psTar, { keyX, keyY, keyInd })
//check psTar
if (size(psTar) === 0) {
return {
err: 'psTar has no effective data'
}
}
// console.log('ptsXYtoArr psTar', psTar)
//interp2Normalize
let itnm = interp2Normalize(psSrc, { keyInd, scale })
// console.log('itnm.st', itnm.st)
// console.log('itnm.psMinMax', itnm.psMinMax)
psSrc = itnm.ps //複寫正規化數據
let nv = itnm.nv
let inv = itnm.inv
// console.log('interp2Normalize psSrc', psSrc)
//psSrcItr
let psSrcItr = [...psSrc]
//psSrcOri
let psSrcOri = [...psSrc]
//kpKnown
let kpKnown = {}
each(psSrc, (v) => {
let k = `${v.x}-${v.y}` //x,y已經被正規化
kpKnown[k] = v.z //x,y,z已經被正規化
})
//intDny
let intDny = Delaunay.from(psSrcOri, d => d.x, d => d.y) //x,y已經被正規化
// //hpg, 原始數據形成最外框邊界
// let hpg = intDny.hullPolygon()
// // console.log('intDny.hullPolygon', hpg)
//vbox
let vbox = [0, 0, scale, scale]
//iniVor
let iniVor = intDny.voronoi(vbox)
// console.log('iniVor', iniVor)
//addPointCore
let addPointCore = (data, x, y, indTar) => {
//psSrcTemp
let psSrcTemp = [...psSrc]
//newItem
let newItem = { x, y, z: 0 } //x,y,z已經被正規化
//push
psSrcTemp.push(newItem)
data.push(newItem)
//postVor
let postVor = Delaunay.from(psSrcTemp, d => d.x, d => d.y).voronoi(vbox) //x,y已經被正規化
// console.log('postVor', postVor)
//newIdx
let newIdx = psSrcTemp.length - 1
// console.log('newIdx', newIdx)
//pgsQuery
let pgsQuery = postVor.cellPolygon(newIdx)
// console.log('pgsQuery', pgsQuery)
//areaQuery
let areaQuery = getAreaPolygon(pgsQuery)
// console.log('areaQuery', areaQuery)
//newPointVal
let newPointVal = null
if (areaQuery > 0) {
//neighborIndices
let neighborIndices = []
// for (let i of postVor.delaunay.neighbors(newIdx)) { //因為postVor.delaunay.neighbors是generator, 考慮要支援ie11得使用while與next讀取
// neighborIndices.push(i)
// }
let sq = postVor.delaunay.neighbors(newIdx)
// console.log(`sq`, sq)
let next = sq.next()
while (!next.done) {
let v = next.value
neighborIndices.push(v)
next = sq.next()
}
// console.log('neighborIndices', neighborIndices)
//subValues
let subValues = map(neighborIndices, (idx) => {
// console.log('idx', idx)
//pgs1, pgs2
let pgs1 = [pgsQuery]
let pgs2 = [iniVor.cellPolygon(idx)]
// console.log('pgs1', pgs1, 'pgs2', pgs2)
//intersect
let pgsInts = []
try {
pgsInts = intersectPolygon(pgs1, pgs2)
// console.log('pgsInts', pgsInts)
}
catch (err) {
console.log('pgs1', pgs1)
console.log('pgs2', pgs2)
console.log(err)
}
//areaInts
let areaInts = 0
if (size(pgsInts) === 1) {
try {
areaInts = getAreaPolygon(pgsInts[0])
//console.log('getAreaPolygon',d3.getAreaPolygon(is.regions[0]),getAreaPolygon(is.regions[0]))
}
catch (err) {
console.log('pgsInts', pgsInts)
console.log(err)
}
}
return {
ia: areaInts,
idx,
}
})
// console.log('subValues', subValues)
//newPointVals
let newPointVals = []
each(subValues, (o) => {
//rA, z, v
let rA = (o.ia / areaQuery)
let z = data[o.idx].z //x,y,z已經被正規化
let v = rA * z
// console.log('subValue', 'rA', rA, 'o.idx', o.idx, 'z', z, 'v', v)
//useFunInterpFragment
if (useFunInterpFragment) {
z = funInterpFragment({
v,
area: o.ia,
areaTotal: areaQuery,
areaRatio: rA,
z,
indSrc: o.idx,
indTar,
// data,
})
}
//p
let p = {
v,
area: o.ia,
areaTotal: areaQuery,
areaRatio: rA,
z,
indSrc: o.idx,
indTar,
}
//push
newPointVals.push(p)
})
//newPointVal
let vt = sum(map(newPointVals, 'v'))
if (useFunInterpFragments) {
vt = funInterpFragments({
ps: newPointVals,
v: vt,
indTar,
})
}
newPointVal = vt
// if (isNaN(newPointVal)) {
// console.log('isNaN(newPointVal)', subValues, 'areaQuery', areaQuery)
// }
// console.log(`areaQuery[${areaQuery}] > 0`, 'newPointVal', newPointVal)
}
//update newPointVal
data[data.length - 1].z = newPointVal //x,y,z已經被正規化
return newPointVal
}
//addPoint
let addPoint = (data, x, y, indTar) => {
//r
let r
try {
r = addPointCore(data, x, y, indTar)
}
catch (err) {
console.log(err)
r = null
data.pop()
}
return r
}
//getNz
let getNz = (nx, ny, indTar) => {
//check
let k = `${nx}-${ny}` //x,y已經被正規化
if (haskey(kpKnown, k)) {
return kpKnown[k]
}
//isPointInPolygon
// if (!isPointInPolygon(hpg, [nx, ny])) {
// return null
// }
if (nx < vbox[0] || nx > vbox[2] || ny < vbox[1] || ny > vbox[3]) {
return null
}
//predict
let nz = addPoint(psSrcItr, nx, ny, indTar)
//save
kpKnown[k] = nz
return nz
}
//itv
let itv = (p, indTar) => {
//x, nx
let x = p.x //x,y已經被正規化
let nx = nv(x, 0) //還原
if (!isNumber(nx)) {
throw new Error(`invalid nx[${nx}]`) //x,y預期都是有值
}
//y, ny
let y = p.y //x,y已經被正規化
let ny = nv(y, 1) //還原
if (!isNumber(ny)) {
throw new Error(`invalid ny[${ny}]`) //x,y預期都是有值
}
//nz, z
let nz = getNz(nx, ny, indTar)
let z = inv(nz, 2) //還原
if (!isNumber(z)) {
z = null //z須支援可能無法內插
}
// console.log('x', x, 'y', y, 'z', z)
// console.log('nx', nx, 'ny', ny, 'nz', nz)
//r
let r = {
[keyX]: x,
[keyY]: y,
[keyZ]: z,
}
return r
}
//r
let r
if (isOne) {
let p = psTar[0]
r = itv(p, 0)
}
else {
r = []
each(psTar, (p, indTar) => {
r.push(itv(p, indTar))
})
}
return r
}
export default interp2NaturalNeighbor