import get from 'lodash-es/get.js'
import each from 'lodash-es/each.js'
import map from 'lodash-es/map.js'
import min from 'lodash-es/min.js'
import max from 'lodash-es/max.js'
import size from 'lodash-es/size.js'
import values from 'lodash-es/values.js'
import cloneDeep from 'lodash-es/cloneDeep.js'
import isestr from 'wsemi/src/isestr.mjs'
import isearr from 'wsemi/src/isearr.mjs'
import isbol from 'wsemi/src/isbol.mjs'
import iseobj from 'wsemi/src/iseobj.mjs'
import isnum from 'wsemi/src/isnum.mjs'
import cdbl from 'wsemi/src/cdbl.mjs'
import ptsXYtoArr from './ptsXYtoArr.mjs'
import { Delaunay } from 'd3-delaunay'
/**
* 計算Voronoi多邊形網格
*
* Unit Test: {@link https://github.com/yuda-lyu/w-gis/blob/master/test/calcVoronoi.test.mjs Github}
* @memberOf w-gis
* @param {Array} points 輸入二維座標加觀測數據點陣列,為[{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 {Number} [opt.scaleBox=1.1] 輸入自動計算外框邊界的x,y上下限放大比例,預設1.1
* @param {Array} [opt.box=[]] 輸入外框邊界,為左上右下的2點座標,若有輸入則強制取消scaleBox,預設[]
* @param {Boolean} [opt.withFinder=false] 輸入是否多回傳查詢x,y座標所在網格函數布林值,預設false
* @param {Boolean} [opt.withInst=false] 輸入是否多回傳D3的Voronoi建構物件布林值,預設false
* @returns {Object} 回傳多邊形網格資訊物件,nodes為多邊形網格之節點,polygons為多邊形網格之節點指標,其位置係基於points產生,故順序亦對應points,若發生錯誤則回傳錯誤訊息物件
* @example
*
* let ps
* let o
* let r
*
* ps = [{ x: 243, y: 206 }, { x: 233, y: 225 }, { x: 21, y: 325 }, { x: 953, y: 283 }, { x: 1092, y: 290 }, { x: 744, y: 200 }, { x: 174, y: 3 }, { x: 537, y: 368 }, { x: 1151, y: 371, ext: 'abc' }, { x: 814, y: 252 }]
* r = calcVoronoi(ps)
* console.log(r)
* // => {
* // nodes: [
* // { x: 492.28293900586067, y: 101.37540698936401 },
* // { x: 386.3692756036636, y: 293.58909242298085 },
* // { x: 98.28090392098173, y: 141.96363364262197 },
* // { x: 491.16970240018804, y: 8.420150415699638 },
* // { x: 341.3003289473684, y: 389.4 },
* // { x: 180.96226415094338, y: 389.4 },
* // { x: 68.03825239785515, y: 150.0010950834529 },
* // { x: -35.5, y: 389.4 },
* // { x: -35.5, y: 100.80434782608694 },
* // { x: 1207.5, y: 60.85114503816796 },
* // { x: 964.0722237025195, y: 189.99794238683128 },
* // { x: 855.7004539898726, y: 122.74938885978698 },
* // { x: 742.008133971292, y: -15.400000000000006 },
* // { x: 1207.5, y: -15.400000000000006 },
* // { x: 936.8158273381295, y: 389.4 },
* // { x: 1207.5, y: 267.858024691358 },
* // { x: 1040.6372881355933, y: 389.4 },
* // { x: 690.3167288225892, y: 345.381326584976 },
* // { x: 499.4022807017544, y: -15.400000000000006 },
* // { x: -35.5, y: -15.400000000000006 },
* // { x: 708.7505415162454, y: 389.4 },
* // { x: 1207.5, y: 389.4 }
* // ],
* // polygons: [
* // [ 0, 1, 2, 3, 0 ],
* // [ 1, 4, 5, 6, 2, 1 ],
* // [ 7, 8, 6, 5, 7 ],
* // [ 9, 10, 11, 12, 13, 9 ],
* // [ 14, 10, 9, 15, 16, 14 ],
* // [
* // 12, 11, 17, 0,
* // 3, 18, 12
* // ],
* // [
* // 19, 18, 3, 2,
* // 6, 8, 19
* // ],
* // [ 4, 1, 0, 17, 20, 4 ],
* // [ 16, 15, 21, 16 ],
* // [ 11, 10, 14, 20, 17, 11 ]
* // ]
* // }
*
* ps = [{ x: 243, y: 206 }, { x: 233, y: 225 }, { x: 21, y: 325 }, { x: 953, y: 283 }, { x: 1092, y: 290 }, { x: 744, y: 200 }, { x: 174, y: 3 }, { x: 537, y: 368 }, { x: 1151, y: 371, ext: 'abc' }, { x: 814, y: 252 }]
* r = calcVoronoi(ps, {
* box: {
* xb0: -100,
* yb0: -50,
* xb1: 1300,
* yb1: 400,
* }
* })
* // console.log(r)
* // => {
* // nodes: [
* // { x: 492.28293900586067, y: 101.37540698936401 },
* // { x: 386.3692756036636, y: 293.58909242298085 },
* // { x: 98.28090392098173, y: 141.96363364262197 },
* // { x: 491.16970240018804, y: 8.420150415699638 },
* // { x: 336.3141447368421, y: 400 },
* // { x: 185.96226415094338, y: 400 },
* // { x: 68.03825239785515, y: 150.0010950834529 },
* // { x: -100, y: 400 },
* // { x: -100, y: 70.15683229813664 },
* // { x: 1300, y: 11.776717557251857 },
* // { x: 964.0722237025195, y: 189.99794238683128 },
* // { x: 855.7004539898726, y: 122.74938885978698 },
* // { x: 713.5334928229665, y: -50 },
* // { x: 1300, y: -50 },
* // { x: 935.3669064748201, y: 400 },
* // { x: 1300, y: 200.4814814814814 },
* // { x: 1026.0847457627117, y: 400 },
* // { x: 690.3167288225892, y: 345.381326584976 },
* // { x: 511.3605263157895, y: -50 },
* // { x: -100, y: -50 },
* // { x: 713.1895306859205, y: 400 },
* // { x: 1300, y: 400 }
* // ],
* // polygons: [
* // [ 0, 1, 2, 3, 0 ],
* // [ 1, 4, 5, 6, 2, 1 ],
* // [ 7, 8, 6, 5, 7 ],
* // [ 9, 10, 11, 12, 13, 9 ],
* // [ 14, 10, 9, 15, 16, 14 ],
* // [
* // 12, 11, 17, 0,
* // 3, 18, 12
* // ],
* // [
* // 19, 18, 3, 2,
* // 6, 8, 19
* // ],
* // [ 4, 1, 0, 17, 20, 4 ],
* // [ 21, 16, 15, 21 ],
* // [ 11, 10, 14, 20, 17, 11 ]
* // ]
* // }
*
* ps = [{ x: 243, y: 206 }, { x: 233, y: 225 }, { x: 21, y: 325 }, { x: 953, y: 283 }, { x: 1092, y: 290 }, { x: 744, y: 200 }, { x: 174, y: 3 }, { x: 537, y: 368 }, { x: 1151, y: 371, ext: 'abc' }, { x: 814, y: 252 }]
* o = calcVoronoi(ps, { withFinder: true })
* r = o.funFindIn({ x: 1151, y: 371, ext: 'abc' })
* console.log(r)
* // => 8
* r = o.funFindIn({ x: 1071, y: 371 }, { returnPoint: true })
* console.log(r)
* // => { x: 1151, y: 371, ext:'abc' }
* r = o.funFindIn({ x: 1061, y: 371 })
* console.log(r)
* // => 4
*
*/
function calcVoronoi(points, opt = {}) {
//check points
if (!isearr(points)) {
return {
err: 'points is not an effective array'
}
}
//keyX
let keyX = get(opt, 'keyX')
if (!isestr(keyX)) {
keyX = 'x'
}
//keyY
let keyY = get(opt, 'keyY')
if (!isestr(keyY)) {
keyY = 'y'
}
//scaleBox
let scaleBox = get(opt, 'scaleBox')
if (!isnum(scaleBox)) {
scaleBox = 1.1
}
scaleBox = cdbl(scaleBox)
//box
let box = get(opt, 'box', {})
let xb0 = get(box, 'xb0', '')
let yb0 = get(box, 'yb0', '')
let xb1 = get(box, 'xb1', '')
let yb1 = get(box, 'yb1', '')
if (iseobj(box)) {
if (!isnum(xb0)) {
throw new Error(`xb0[${xb0}] is not a number`)
}
if (!isnum(yb0)) {
throw new Error(`yb0[${yb0}] is not a number`)
}
if (!isnum(xb1)) {
throw new Error(`xb1[${xb1}] is not a number`)
}
if (!isnum(yb1)) {
throw new Error(`yb1[${yb1}] is not a number`)
}
}
//withFinder
let withFinder = get(opt, 'withFinder')
if (!isbol(withFinder)) {
withFinder = false
}
//withInst
let withInst = get(opt, 'withInst')
if (!isbol(withInst)) {
withInst = false
}
//pointsOri
let pointsOri = cloneDeep(points)
//keyInd
let keyInd = 'ind'
//ptsXYtoArr
points = ptsXYtoArr(points, { keyX, keyY, keyInd })
//check points
if (size(points) === 0) {
return {
err: 'points has no effective data'
}
}
// console.log('ptsXYtoArr points', points)
//gtlim
let gtlim = (key) => {
let vs = map(points, key)
let _min = min(vs)
let _max = max(vs)
return {
min: _min,
max: _max,
}
}
//xmin
let _x = gtlim('x')
let xmin = _x.min
let xmax = _x.max
let xcenter = (xmin + xmax) / 2
let xrange = xmax - xmin
//ymin
let _y = gtlim('y')
let ymin = _y.min
let ymax = _y.max
let ycenter = (ymin + ymax) / 2
let yrange = ymax - ymin
//xb0,yb0,xb1,yb1 by scaleBox
if (!iseobj(box)) {
xb0 = xcenter - xrange / 2 * scaleBox
xb1 = xcenter + xrange / 2 * scaleBox
yb0 = ycenter - yrange / 2 * scaleBox
yb1 = ycenter + yrange / 2 * scaleBox
}
//intDny
let intDny = Delaunay.from(points, d => d.x, d => d.y)
// console.log('intDny', intDny)
//vbox
let vbox = [xb0, yb0, xb1, yb1]
// console.log('vbox', vbox)
//iniVor
let iniVor = intDny.voronoi(vbox)
// console.log('iniVor', iniVor)
//pps
let pps = []
if (true) {
let i = -1
while (true) {
i++
let ps = iniVor.cellPolygon(i)
if (!isnum(get(ps, `0.0`))) {
break
}
pps.push(ps)
}
}
//vpoints
let vpoints = []
if (true) {
let kp = {}
each(pps, (ps) => {
each(ps, (p) => {
let x = p[0]
let y = p[1]
let key = `${x}:${y}`
kp[key] = { x, y }
})
})
vpoints = values(kp)
}
// console.log('vpoints', vpoints)
//kpVpoints
let kpVpoints = {}
each(vpoints, (v, ind) => {
let key = `${v.x}:${v.y}`
kpVpoints[key] = ind
})
// console.log('kpVpoints', kpVpoints)
//vpolygons
let vpolygons = []
let _vpolygons = []
if (true) {
each(pps, (ps) => {
let inds = []
let _ps = []
each(ps, (p) => {
let x = p[0]
let y = p[1]
let key = `${x}:${y}`
let ind = kpVpoints[key]
inds.push(ind)
_ps.push({ x, y })
})
vpolygons.push(inds)
_vpolygons.push(_ps)
})
}
// console.log('vpolygons', vpolygons)
// each(points, (p, i) => {
// // let _p = get(_vpolygons, i)
// let b = iniVor.contains(i, p.x, p.y)
// console.log(i, p, b, _p)
// })
//funFindIn
let funFindIn = (p, opt = {}) => {
if (!iseobj(p)) {
throw new Error(`p[${p}] is not an effective object`)
}
let x = get(p, 'x')
if (!isnum(x)) {
throw new Error(`x[${x}] is not a number`)
}
let y = get(p, 'y')
if (!isnum(y)) {
throw new Error(`y[${y}] is not a number`)
}
let returnPoint = get(opt, 'returnPoint')
if (!isbol(returnPoint)) {
returnPoint = false
}
let r = intDny.find(x, y) //基於Delaunay查找最近點
if (returnPoint) {
r = get(pointsOri, r)
}
return r
}
//r
let r = {
nodes: vpoints,
polygons: vpolygons,
}
if (withFinder) {
r.funFindIn = funFindIn
}
if (withInst) {
r.inst = iniVor
}
return r
}
export default calcVoronoi