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 get from 'lodash-es/get.js'
import take from 'lodash-es/take.js'
import size from 'lodash-es/size.js'
import cloneDeep from 'lodash-es/cloneDeep.js'
import isearr from 'wsemi/src/isearr.mjs'
import isestr from 'wsemi/src/isestr.mjs'
import isfun from 'wsemi/src/isfun.mjs'
import ispnum from 'wsemi/src/ispnum.mjs'
import isbol from 'wsemi/src/isbol.mjs'
import iseobj from 'wsemi/src/iseobj.mjs'
import cdbl from 'wsemi/src/cdbl.mjs'
import oc from 'wsemi/src/color.mjs'
import { tricontour } from 'd3-tricontour'
import ptsXYZtoArr from './ptsXYZtoArr.mjs'
import getAreaMultiPolygonSm from './getAreaMultiPolygonSm.mjs'
import getCentroidMultiPolygon from './getCentroidMultiPolygon.mjs'
import clipMultiPolygon from './clipMultiPolygon.mjs'
import intersectMultiPolygon from './intersectMultiPolygon.mjs'
import invCoordMultiPolygon from './invCoordMultiPolygon.mjs'
function getContours(pts, thresholds) {
//tricontour
let tric = tricontour()
//thresholds
if (isearr(thresholds)) {
tric.thresholds(thresholds) //[0, 25, 50]
}
//calc
let contours = tric(pts)
// console.log('calcContours contours1', contours)
// contours = [contours[0], contours[1], contours[2], contours[3]]
// console.log('calcContours contours2', contours)
//偵測從最末數來第一個無效多邊形
let ub = size(contours) - 1
for (let i = ub; i >= 1; i--) {
//ipre
let ipre = i - 1
//contourPre
let contourPre = contours[ipre]
//coordinatesPre
let coordinatesPre = get(contourPre, 'coordinates', [])
//nPre
let nPre = size(coordinatesPre)
//contourNow
let contourNow = contours[i]
//coordinatesNow
let coordinatesNow = get(contourNow, 'coordinates', [])
//nNow
let nNow = size(coordinatesNow)
//check
if (nPre > 0 && nNow === 0) {
//找到從最末數來第一個無效多邊形
//set sepZone=top
contours[i].sepZone = 'top'
//take
contours = take(contours, i + 1)
break
}
}
return contours
}
/**
* 不規則點基於三角化網格計算等值線圖
*
* Unit Test: {@link https://github.com/yuda-lyu/w-gis/blob/master/test/calcContours.test.mjs Github}
* @memberOf w-gis
* @param {Array} points 輸入點陣列,各點可為[{x:x1,y:y1},{x:x2,y:y2},...]物件型態,或可為[[x1,y1],[x2,y2],...]陣列型態
* @param {String} [opt.keyX='x'] 輸入點物件之x座標欄位字串,預設'x'
* @param {String} [opt.keyY='y'] 輸入點物件之y座標欄位字串,預設'y'
* @param {String} [opt.keyZ='z'] 輸入點物件之z座標或值欄位字串,預設'z'
* @param {Array} [opt.containInner=null] 輸入等值線圖須保留之MultiPolygon陣列,也就是等值線圖取交集,預設null
* @param {Array} [opt.clipInner=null] 輸入等值線圖須剔除以內之MultiPolygon陣列,也就是等值線圖取差集,預設null
* @param {Array} [opt.clipOuter=null] 輸入等值線圖須剔除以外之MultiPolygon陣列,效果同containInner是等值線圖取交集,預設null
* @param {Array} [opt.thresholds=null] 輸入指定等值線切分值陣列,預設null
* @param {Boolean} [opt.withStyle=false] 輸入是否給予樣式布林值,若returnGeojson給予true也會強制給予樣式,預設false
* @param {Function} [opt.kpGradientColor={0: 'rgb(255, 255, 255)',0.2: 'rgb(254, 178, 76)',0.4: 'rgb(252, 78, 42)',0.6: 'rgb(220, 58, 38)',0.8: 'rgb(200, 40, 23)',1: 'rgb(180, 30, 60)'}] 輸入內插顏色用梯度物件,預設{0: 'rgb(255, 255, 255)',0.2: 'rgb(254, 178, 76)',0.4: 'rgb(252, 78, 42)',0.6: 'rgb(220, 58, 38)',0.8: 'rgb(200, 40, 23)',1: 'rgb(180, 30, 60)'}
* @param {Function} [opt.funGetFillColor=null] 輸入內插面顏色用函數,輸入為(k,n),分別代表當前等值線指標與最大指標(也就是等值線數-1),不提供函數時使用預設kpGradientColor進行內插,預設null
* @param {Number} [opt.fillOpacity=0.2] 輸入面顏色透明度數字,預設0.2
* @param {Number} [opt.lineColor=''] 輸入線顏色字串,若有funGetLineColor則優先使用,若無則預設使用面顏色,預設''
* @param {Function} [opt.funGetLineColor=null] 輸入內插線顏色用函數,若有給予則覆蓋lineColor,輸入為(k,n),分別代表當前等值線指標與最大指標(也就是等值線數-1),不提供函數時使用預設為面顏色,預設null
* @param {Number} [opt.lineOpacity=1] 輸入線顏色透明度數字,預設1
* @param {Number} [opt.lineWidth=1] 輸入線寬度數字,預設1
* @param {Boolean} [opt.returnGeojson=false] 輸入是否回傳GeoJSON布林值,若為true會強制withStyle給予true,預設false
* @param {Boolean} [opt.inverseCoordinate=false] 輸入是否交換經緯度布林值,因GeoJSON之各點為經緯度,而Leaflet為緯經度,若座標來源與輸出須交換經緯度則使用此設定。預設false
* @returns {Array|Object} 回傳點物件陣列或點物件,若使用returnWithVariogram=true則回傳物件資訊,若發生錯誤則回傳錯誤訊息物件
* @example
*
* let opt
* let pgs
*
* let points = [
* [24.325, 120.786, 0], [23.944, 120.968, 10], [24.884, 121.234, 20], [24.579, 121.345, 80], [24.664, 121.761, 40], [23.803, 121.397, 30],
* [23.727, 120.772, 0], [23.539, 120.975, 0], [23.612, 121.434, 0],
* [23.193, 120.355, 22], [23.456, 120.890, 42], [23.280, 120.551, 25], [23.162, 121.247, 5],
* ]
*
* let containInner = [ //此結構代表1個polygon, leaflet可支援顯示, 但turf做intersect不支援, 故l-contour會通過toMultiPolygon轉換才能支援
* [
* [24.28, 120.842], [24.494, 121.203], [24.314, 121.190], [24.232, 121.109], [24.249, 120.910],
* ],
* [
* [24.217, 120.851], [24.172, 121.242], [24.059, 121.333], [24.001, 121.055],
* ],
* ]
*
* let clipInner = [ //此結構代表1個polygon, leaflet可支援顯示, 但turf做difference不支援, 故l-contour會通過toMultiPolygon轉換才能支援
* [
* [24.28, 120.842], [24.494, 121.203], [24.314, 121.190], [24.232, 121.109], [24.249, 120.910],
* ],
* [
* [24.217, 120.851], [24.172, 121.242], [24.059, 121.333], [24.001, 121.055],
* ],
* ]
*
* let clipOuter = [
* [24.585, 120.79], [24.9, 121.620], [23.984, 121.6], [23.941, 121.196], [24.585, 120.79]
* ]
*
* let thresholds = [0, 5, 10, 20, 30, 40, 55, 70, 85]
*
* opt = {
* containInner,
* // clipInner,
* // clipOuter,
* // thresholds,
* }
* pgs = calcContours(points, opt)
* fs.writeFileSync('./calcContours1.json', JSON.stringify(pgs), 'utf8')
* console.log(pgs)
* // => [
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array] ],
* // effectArea: 8816415983.520641,
* // effectAreaCentroid: [ 23.973333333333333, 121.13616666666665 ],
* // range: { text: '0 - 10', low: 0, up: 10 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array], [Array] ],
* // effectArea: 6313849792.51668,
* // effectAreaCentroid: [ 23.80531082115246, 121.00212446033244 ],
* // range: { text: '10 - 20', low: 10, up: 20 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array] ],
* // effectArea: 4644527033.145393,
* // effectAreaCentroid: [ 23.791076270349798, 120.96719391394832 ],
* // range: { text: '20 - 30', low: 20, up: 30 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array] ],
* // effectArea: 3211476473.817216,
* // effectAreaCentroid: [ 24.025569342289934, 121.2215115677248 ],
* // range: { text: '30 - 40', low: 30, up: 40 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array] ],
* // effectArea: 2077979122.6009896,
* // effectAreaCentroid: [ 24.155744629924033, 121.28620957869633 ],
* // range: { text: '40 - 50', low: 40, up: 50 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array] ],
* // effectArea: 1165016840.7246127,
* // effectAreaCentroid: [ 24.45565142857143, 121.3283007142857 ],
* // range: { text: '50 - 60', low: 50, up: 60 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // }
* // ]
*
* opt = {
* // containInner,
* clipInner,
* // clipOuter,
* // thresholds,
* }
* pgs = calcContours(points, opt)
* fs.writeFileSync('./calcContours2.json', JSON.stringify(pgs), 'utf8')
* console.log(pgs)
* // => [
* // {
* // sepZone: '',
* // latLngs: [ [Array] ],
* // effectArea: 8816415983.520641,
* // effectAreaCentroid: [ 23.973333333333333, 121.13616666666665 ],
* // range: { text: '0 - 10', low: 0, up: 10 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array], [Array], [Array] ],
* // effectArea: 6313849792.51668,
* // effectAreaCentroid: [ 23.80531082115246, 121.00212446033244 ],
* // range: { text: '10 - 20', low: 10, up: 20 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array], [Array], [Array], [Array] ],
* // effectArea: 4644527033.145393,
* // effectAreaCentroid: [ 23.791076270349798, 120.96719391394832 ],
* // range: { text: '20 - 30', low: 20, up: 30 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array], [Array], [Array] ],
* // effectArea: 3211476473.817216,
* // effectAreaCentroid: [ 24.025569342289934, 121.2215115677248 ],
* // range: { text: '30 - 40', low: 30, up: 40 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array] ],
* // effectArea: 2077979122.6009896,
* // effectAreaCentroid: [ 24.155744629924033, 121.28620957869633 ],
* // range: { text: '40 - 50', low: 40, up: 50 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array] ],
* // effectArea: 1165016840.7246127,
* // effectAreaCentroid: [ 24.45565142857143, 121.3283007142857 ],
* // range: { text: '50 - 60', low: 50, up: 60 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array] ],
* // effectArea: 517569529.67260885,
* // effectAreaCentroid: [ 24.49676761904762, 121.33386714285714 ],
* // range: { text: '60 - 70', low: 60, up: 70 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: 'top',
* // latLngs: [ [Array] ],
* // effectArea: 129338350.80859056,
* // effectAreaCentroid: [ 24.537883809523812, 121.33943357142857 ],
* // range: { text: '70 - 80', low: 70, up: 80 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // }
* // ]
*
* opt = {
* // containInner,
* // clipInner,
* clipOuter,
* // thresholds,
* }
* pgs = calcContours(points, opt)
* fs.writeFileSync('./calcContours3.json', JSON.stringify(pgs), 'utf8')
* console.log(pgs)
* // => [
* // {
* // sepZone: '',
* // latLngs: [ [Array] ],
* // effectArea: 8816415983.520641,
* // effectAreaCentroid: [ 23.973333333333333, 121.13616666666665 ],
* // range: { text: '0 - 10', low: 0, up: 10 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array] ],
* // effectArea: 6313849792.51668,
* // effectAreaCentroid: [ 23.80531082115246, 121.00212446033244 ],
* // range: { text: '10 - 20', low: 10, up: 20 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array], [Array], [Array] ],
* // effectArea: 4644527033.145393,
* // effectAreaCentroid: [ 23.791076270349798, 120.96719391394832 ],
* // range: { text: '20 - 30', low: 20, up: 30 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array], [Array] ],
* // effectArea: 3211476473.817216,
* // effectAreaCentroid: [ 24.025569342289934, 121.2215115677248 ],
* // range: { text: '30 - 40', low: 30, up: 40 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array] ],
* // effectArea: 2077979122.6009896,
* // effectAreaCentroid: [ 24.155744629924033, 121.28620957869633 ],
* // range: { text: '40 - 50', low: 40, up: 50 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array] ],
* // effectArea: 1165016840.7246127,
* // effectAreaCentroid: [ 24.45565142857143, 121.3283007142857 ],
* // range: { text: '50 - 60', low: 50, up: 60 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array] ],
* // effectArea: 517569529.67260885,
* // effectAreaCentroid: [ 24.49676761904762, 121.33386714285714 ],
* // range: { text: '60 - 70', low: 60, up: 70 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: 'top',
* // latLngs: [ [Array] ],
* // effectArea: 129338350.80859056,
* // effectAreaCentroid: [ 24.537883809523812, 121.33943357142857 ],
* // range: { text: '70 - 80', low: 70, up: 80 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // }
* // ]
*
* opt = {
* // containInner,
* // clipInner,
* // clipOuter,
* thresholds,
* }
* pgs = calcContours(points, opt)
* fs.writeFileSync('./calcContours4.json', JSON.stringify(pgs), 'utf8')
* console.log(pgs)
* // => [
* // {
* // sepZone: '',
* // latLngs: [ [Array] ],
* // effectArea: 8816415983.520641,
* // effectAreaCentroid: [ 23.973333333333333, 121.13616666666665 ],
* // range: { text: '0 - 5', low: 0, up: 5 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array] ],
* // effectArea: 7186533231.875366,
* // effectAreaCentroid: [ 23.802593637502845, 121.01686739291412 ],
* // range: { text: '5 - 10', low: 5, up: 10 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array] ],
* // effectArea: 6313849792.51668,
* // effectAreaCentroid: [ 23.80531082115246, 121.00212446033244 ],
* // range: { text: '10 - 20', low: 10, up: 20 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array] ],
* // effectArea: 4644527033.145393,
* // effectAreaCentroid: [ 23.791076270349798, 120.96719391394832 ],
* // range: { text: '20 - 30', low: 20, up: 30 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array] ],
* // effectArea: 3211476473.817216,
* // effectAreaCentroid: [ 24.025569342289934, 121.2215115677248 ],
* // range: { text: '30 - 40', low: 30, up: 40 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array] ],
* // effectArea: 2077979122.6009896,
* // effectAreaCentroid: [ 24.155744629924033, 121.28620957869633 ],
* // range: { text: '40 - 55', low: 40, up: 55 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array] ],
* // effectArea: 808871008.3417139,
* // effectAreaCentroid: [ 24.476209523809523, 121.33108392857142 ],
* // range: { text: '55 - 70', low: 55, up: 70 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // },
* // {
* // sepZone: 'top',
* // latLngs: [ [Array] ],
* // effectArea: 129338350.80859056,
* // effectAreaCentroid: [ 24.537883809523812, 121.33943357142857 ],
* // range: { text: '70 - 85', low: 70, up: 85 },
* // center: [ 23.973333333333333, 121.13616666666665 ]
* // }
* // ]
*
* opt = {
* withStyle: true,
* // returnGeojson: true,
* }
* pgs = calcContours(points, opt)
* fs.writeFileSync('./calcContours5.json', JSON.stringify(pgs), 'utf8')
* console.log(pgs)
* // => [
* // {
* // sepZone: '',
* // latLngs: [ [Array] ],
* // effectArea: 8816415983.520641,
* // effectAreaCentroid: [ 23.973333333333333, 121.13616666666665 ],
* // range: { text: '0 - 10', low: 0, up: 10 },
* // center: [ 23.973333333333333, 121.13616666666665 ],
* // style: {
* // color: 'rgba(255, 255, 255, 1)',
* // weight: 1,
* // fillColor: 'rgba(255, 255, 255, 1)',
* // fillOpacity: 0.2,
* // stroke: 'rgba(255, 255, 255, 1)',
* // 'stroke-width': 1,
* // 'stroke-opacity': 1,
* // fill: 'rgba(255, 255, 255, 1)',
* // 'fill-opacity': 0.2
* // }
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array] ],
* // effectArea: 6313849792.51668,
* // effectAreaCentroid: [ 23.80531082115246, 121.00212446033244 ],
* // range: { text: '10 - 20', low: 10, up: 20 },
* // center: [ 23.973333333333333, 121.13616666666665 ],
* // style: {
* // color: 'rgba(254, 200, 127, 1)',
* // weight: 1,
* // fillColor: 'rgba(254, 200, 127, 1)',
* // fillOpacity: 0.2,
* // stroke: 'rgba(254, 200, 127, 1)',
* // 'stroke-width': 1,
* // 'stroke-opacity': 1,
* // fill: 'rgba(254, 200, 127, 1)',
* // 'fill-opacity': 0.2
* // }
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array] ],
* // effectArea: 4644527033.145393,
* // effectAreaCentroid: [ 23.791076270349798, 120.96719391394832 ],
* // range: { text: '20 - 30', low: 20, up: 30 },
* // center: [ 23.973333333333333, 121.13616666666665 ],
* // style: {
* // color: 'rgba(253, 135, 61, 1)',
* // weight: 1,
* // fillColor: 'rgba(253, 135, 61, 1)',
* // fillOpacity: 0.2,
* // stroke: 'rgba(253, 135, 61, 1)',
* // 'stroke-width': 1,
* // 'stroke-opacity': 1,
* // fill: 'rgba(253, 135, 61, 1)',
* // 'fill-opacity': 0.2
* // }
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array] ],
* // effectArea: 3211476473.817216,
* // effectAreaCentroid: [ 24.025569342289934, 121.2215115677248 ],
* // range: { text: '30 - 40', low: 30, up: 40 },
* // center: [ 23.973333333333333, 121.13616666666665 ],
* // style: {
* // color: 'rgba(247, 75, 41, 1)',
* // weight: 1,
* // fillColor: 'rgba(247, 75, 41, 1)',
* // fillOpacity: 0.2,
* // stroke: 'rgba(247, 75, 41, 1)',
* // 'stroke-width': 1,
* // 'stroke-opacity': 1,
* // fill: 'rgba(247, 75, 41, 1)',
* // 'fill-opacity': 0.2
* // }
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array], [Array] ],
* // effectArea: 2077979122.6009896,
* // effectAreaCentroid: [ 24.155744629924033, 121.28620957869633 ],
* // range: { text: '40 - 50', low: 40, up: 50 },
* // center: [ 23.973333333333333, 121.13616666666665 ],
* // style: {
* // color: 'rgba(225, 61, 39, 1)',
* // weight: 1,
* // fillColor: 'rgba(225, 61, 39, 1)',
* // fillOpacity: 0.2,
* // stroke: 'rgba(225, 61, 39, 1)',
* // 'stroke-width': 1,
* // 'stroke-opacity': 1,
* // fill: 'rgba(225, 61, 39, 1)',
* // 'fill-opacity': 0.2
* // }
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array] ],
* // effectArea: 1165016840.7246127,
* // effectAreaCentroid: [ 24.45565142857143, 121.3283007142857 ],
* // range: { text: '50 - 60', low: 50, up: 60 },
* // center: [ 23.973333333333333, 121.13616666666665 ],
* // style: {
* // color: 'rgba(209, 48, 29, 1)',
* // weight: 1,
* // fillColor: 'rgba(209, 48, 29, 1)',
* // fillOpacity: 0.2,
* // stroke: 'rgba(209, 48, 29, 1)',
* // 'stroke-width': 1,
* // 'stroke-opacity': 1,
* // fill: 'rgba(209, 48, 29, 1)',
* // 'fill-opacity': 0.2
* // }
* // },
* // {
* // sepZone: '',
* // latLngs: [ [Array] ],
* // effectArea: 517569529.67260885,
* // effectAreaCentroid: [ 24.49676761904762, 121.33386714285714 ],
* // range: { text: '60 - 70', low: 60, up: 70 },
* // center: [ 23.973333333333333, 121.13616666666665 ],
* // style: {
* // color: 'rgba(194, 37, 34, 1)',
* // weight: 1,
* // fillColor: 'rgba(194, 37, 34, 1)',
* // fillOpacity: 0.2,
* // stroke: 'rgba(194, 37, 34, 1)',
* // 'stroke-width': 1,
* // 'stroke-opacity': 1,
* // fill: 'rgba(194, 37, 34, 1)',
* // 'fill-opacity': 0.2
* // }
* // },
* // {
* // sepZone: 'top',
* // latLngs: [ [Array] ],
* // effectArea: 129338350.80859056,
* // effectAreaCentroid: [ 24.537883809523812, 121.33943357142857 ],
* // range: { text: '70 - 80', low: 70, up: 80 },
* // center: [ 23.973333333333333, 121.13616666666665 ],
* // style: {
* // color: 'rgba(180, 30, 60, 1)',
* // weight: 1,
* // fillColor: 'rgba(180, 30, 60, 1)',
* // fillOpacity: 0.2,
* // stroke: 'rgba(180, 30, 60, 1)',
* // 'stroke-width': 1,
* // 'stroke-opacity': 1,
* // fill: 'rgba(180, 30, 60, 1)',
* // 'fill-opacity': 0.2
* // }
* // }
* // ]
*
* opt = {
* withStyle: true,
* returnGeojson: true,
* }
* pgs = calcContours(points, opt)
* fs.writeFileSync('./calcContours6.json', JSON.stringify(pgs), 'utf8')
* console.log(pgs)
* // => {
* // type: 'FeatureCollection',
* // features: [
* // { type: 'Feature', properties: [Object], geometry: [Object] },
* // { type: 'Feature', properties: [Object], geometry: [Object] },
* // { type: 'Feature', properties: [Object], geometry: [Object] },
* // { type: 'Feature', properties: [Object], geometry: [Object] },
* // { type: 'Feature', properties: [Object], geometry: [Object] },
* // { type: 'Feature', properties: [Object], geometry: [Object] },
* // { type: 'Feature', properties: [Object], geometry: [Object] },
* // { type: 'Feature', properties: [Object], geometry: [Object] }
* // ]
* // }
*
* opt = {
* withStyle: true,
* returnGeojson: true,
* inverseCoordinate: true,
* }
* pgs = calcContours(points, opt)
* fs.writeFileSync('./calcContours7.json', JSON.stringify(pgs), 'utf8')
* console.log(pgs)
* // => {
* // type: 'FeatureCollection',
* // features: [
* // { type: 'Feature', properties: [Object], geometry: [Object] },
* // { type: 'Feature', properties: [Object], geometry: [Object] },
* // { type: 'Feature', properties: [Object], geometry: [Object] },
* // { type: 'Feature', properties: [Object], geometry: [Object] },
* // { type: 'Feature', properties: [Object], geometry: [Object] },
* // { type: 'Feature', properties: [Object], geometry: [Object] },
* // { type: 'Feature', properties: [Object], geometry: [Object] },
* // { type: 'Feature', properties: [Object], geometry: [Object] }
* // ]
* // }
*
*/
function calcContours(points, opt = {}) {
//check psSrc
if (!isearr(points)) {
return {
err: 'points is not an array'
}
}
//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'
}
//withStyle
let withStyle = get(opt, 'withStyle')
if (!isbol(withStyle)) {
withStyle = false
}
//returnGeojson
let returnGeojson = get(opt, 'returnGeojson')
if (!isbol(returnGeojson)) {
returnGeojson = false
}
//inverseCoordinate
let inverseCoordinate = get(opt, 'inverseCoordinate')
if (!isbol(inverseCoordinate)) {
inverseCoordinate = false
}
//ptsXYZtoArr
points = ptsXYZtoArr(points, { ...opt, returnObjArray: false })
//containInner
let containInner = get(opt, 'containInner', null)
//clipInner
let clipInner = get(opt, 'clipInner', null)
//clipOuter
let clipOuter = get(opt, 'clipOuter', null)
//thresholds
let thresholds = get(opt, 'thresholds', null)
//useThresholds
let useThresholds = isearr(thresholds)
//valueMin, valueMax
let valueMin = points[0][2]
let valueMax = points[0][2]
for (let i = 1; i < size(points); i++) {
let value = points[i][2]
if (valueMin > value) {
valueMin = value
}
if (valueMax < value) {
valueMax = value
}
}
// console.log('valueMin', valueMin, 'valueMax', valueMax)
//contours
let contours = getContours(points, thresholds)
// console.log('contours', cloneDeep(contours), 'thresholds', thresholds)
//check
if (!isearr(contours)) {
console.log('points', points)
console.log('thresholds', thresholds)
return {
err: 'can not calculate contours'
}
}
// console.log('contours.length', contours.length)
//polylines
let polylines = map(contours, (v, k) => {
return {
sepZone: get(v, 'sepZone', ''),
latLngs: v.coordinates,
level: v.value,
effectArea: getAreaMultiPolygonSm(v.coordinates),
effectAreaCentroid: getCentroidMultiPolygon(v.coordinates), //要先計算, 否則之後會被相減計算成真實等值區域, 就不是影響區域的中心了
}
})
// console.log('polylines from contours', cloneDeep(polylines))
//check, 若沒有設定thresholds則不需要補虛擬polyline
if (!useThresholds) {
//pmin, pmax
let ls = map(polylines, 'level')
let pmin = min(ls)
let pmax = max(ls)
// console.log('pmin', pmin, 'pmax', pmax)
//實際數據有超出contours的最大值, 添加虛擬polyline
if (pmax < valueMax) {
console.log('非預期: pmax < valueMax', pmax < valueMax, pmax, valueMax)
polylines.push({
mode: 'virtualEnd',
sepZone: '',
latLngs: [],
level: valueMax,
effectArea: 0,
effectAreaCentroid: null,
})
}
//實際數據有低於contours的最小值, 添加虛擬polyline
if (pmin > valueMin) {
console.log('非預期: pmin > valueMin', pmin > valueMin, pmin, valueMin)
let pl = {
mode: 'virtualStart',
sepZone: '',
latLngs: [],
level: valueMin,
effectArea: 0,
effectAreaCentroid: null,
}
polylines = [pl, ...polylines]
}
}
// console.log('polylines for vartual level', cloneDeep(polylines))
//若沒有設定thresholds則自動修正level, 因tricontour會給出繪圖間距而不是實際資料間距, 故會出現level值大於或小於原數據上下限
if (!useThresholds) {
each(polylines, (v, k) => {
v.level = Math.min(v.level, valueMax)
v.level = Math.max(v.level, valueMin)
})
}
// console.log('polylines for limit level', cloneDeep(polylines))
//polygonSets, 剔除下1個多邊形區域, 為實際需繪製的等值區
let polygonSets = []
for (let i = 0; i <= polylines.length - 2; i++) {
//p0, p1
let p0 = polylines[i]
let p1 = polylines[i + 1]
// console.log(i, 'p0', p0, p0.level, 'p1', p1, p1.level)
//latLngs0, latLngs1
let latLngs0 = p0.latLngs
let latLngs1 = p1.latLngs
//range
let range = {
text: `${polylines[i].level} - ${polylines[i + 1].level}`,
low: polylines[i].level,
up: polylines[i + 1].level,
}
//clipMultiPolygon
let latLngs = []
latLngs = clipMultiPolygon(latLngs0, latLngs1)
if (p0.mode === 'virtualStart') { //若為virtualStart, 則代表直接使用下1個polylines成為等值區域, 方能代表凹陷區
latLngs = latLngs1 //因為既有屬性都是取前者, 故若virtualStart係使用後者p1, 就代表全部polygonSet都會有真實effectArea
}
else if (p1.sepZone === 'top') { //若後者sepZone為top, 代表無多邊形數據, 得要使用前者多邊形建構
latLngs = latLngs0
}
else {
latLngs = clipMultiPolygon(latLngs0, latLngs1)
}
//p
let p = cloneDeep(p0)
//delete level, 已轉成range
delete p.level
//ps
let ps = {
...p,
sepZone: get(p1, 'sepZone', ''),
latLngs,
range,
}
//push
polygonSets.push(ps)
}
// console.log('polygonSets from polylines', cloneDeep(polygonSets))
//polygonsContainInner, 保留指定多polygon以內區域
if (true) {
let t = []
each(polygonSets, (polygonSet, k) => {
//latLngs
let latLngs = null
if (isearr(containInner)) {
//intersectMultiPolygon
latLngs = intersectMultiPolygon(polygonSet.latLngs, containInner)
}
else {
latLngs = polygonSet.latLngs
}
//check
if (size(latLngs) > 0) {
t.push({
...polygonSet,
latLngs,
})
}
})
polygonSets = t
}
// console.log('polygonSets for polygonsContainInner', cloneDeep(polygonSets))
//polygonsClipInner, 剔除指定多polygon以內區域
if (true) {
let t = []
each(polygonSets, (polygonSet, k) => {
//latLngs
let latLngs = null
if (isearr(clipInner)) {
//clipMultiPolygon
latLngs = clipMultiPolygon(polygonSet.latLngs, clipInner)
}
else {
latLngs = polygonSet.latLngs
}
//check
if (size(latLngs) > 0) {
t.push({
...polygonSet,
latLngs,
})
}
})
polygonSets = t
}
// console.log('polygonSets for polygonsClipInner', cloneDeep(polygonSets))
//polygonClipOuter, 剔除指定polygon以外區域
if (true) {
let t = []
each(polygonSets, (polygonSet) => {
//latLngs
let latLngs = null
if (isearr(clipOuter)) {
//intersectMultiPolygon
latLngs = intersectMultiPolygon(polygonSet.latLngs, clipOuter)
}
else {
latLngs = polygonSet.latLngs
}
//check
if (size(latLngs) > 0) {
t.push({
...polygonSet,
latLngs,
})
}
})
polygonSets = t
}
// console.log('polygonSets for polygonClipOuter', cloneDeep(polygonSets))
//center
if (true) {
let areaMax = 0
let areaInd = null
let areaCentroid = null
each(polygonSets, (polygonSet, k) => {
if (areaMax < polygonSet.effectArea) {
areaInd = k
areaMax = polygonSet.effectArea
areaCentroid = polygonSet.effectAreaCentroid
}
})
if (areaInd === null) {
console.log('polygonSets', polygonSets)
return {
err: 'can not calculate centroid of contour'
}
}
//add center
each(polygonSets, (polygonSet, k) => {
polygonSets[k].center = areaCentroid
})
}
//withStyle
if (withStyle || returnGeojson) {
//kpGradientColor
let kpGradientColor = get(opt, 'kpGradientColor', null)
if (!iseobj(kpGradientColor)) {
kpGradientColor = {
0: 'rgb(255, 255, 255)',
0.2: 'rgb(254, 178, 76)',
0.4: 'rgb(252, 78, 42)',
0.6: 'rgb(220, 58, 38)',
0.8: 'rgb(200, 40, 23)',
1: 'rgb(180, 30, 60)',
}
}
//funGetFillColor
let funGetFillColor = get(opt, 'funGetFillColor', null)
if (!isfun(funGetFillColor)) {
let fun = oc.interp(kpGradientColor)
funGetFillColor = (k, n) => {
let c = ''
if (n > 0) {
c = fun(k / n)
}
else {
c = fun(1)
}
return c
}
}
//fillOpacity
let fillOpacity = get(opt, 'fillOpacity', null)
if (!ispnum(fillOpacity)) {
fillOpacity = 0.2
}
fillOpacity = cdbl(fillOpacity)
//lineColor
let lineColor = get(opt, 'lineColor', null)
if (!isestr(lineColor)) {
lineColor = ''
}
//funGetLineColor
let funGetLineColor = get(opt, 'funGetLineColor', null)
//lineOpacity
let lineOpacity = get(opt, 'lineOpacity', null)
if (!ispnum(lineOpacity)) {
lineOpacity = 1
}
lineOpacity = cdbl(lineOpacity)
//lineWidth
let lineWidth = get(opt, 'lineWidth', null)
if (!ispnum(lineWidth)) {
lineWidth = 1
}
lineWidth = cdbl(lineWidth)
//add style
let ub = polygonSets.length - 1
polygonSets = map(polygonSets, (polygonSet, k) => {
// console.log(k, 'polygonSet', polygonSet)
//fillColor
let fillColor = funGetFillColor(k, ub)
// console.log('fillColor', fillColor)
//strokeColor
let strokeColor = ''
if (isfun(funGetLineColor)) {
strokeColor = funGetLineColor(k, ub)
// console.log('strokeColor(funGetLineColor)', strokeColor)
}
else if (isestr(lineColor)) {
strokeColor = lineColor
// console.log('strokeColor(lineColor)', strokeColor)
}
else {
strokeColor = fillColor
// console.log('strokeColor(fillColor)', strokeColor)
}
// console.log('strokeColor', strokeColor)
//strokeWidth
let strokeWidth = lineWidth
//strokeOpacity
let strokeOpacity = lineOpacity
//style
let style = {
//leaflet
'color': strokeColor,
'weight': strokeWidth,
fillColor,
fillOpacity,
//svg, mapbox
'stroke': strokeColor,
'stroke-width': strokeWidth,
'stroke-opacity': strokeOpacity,
'fill': fillColor,
'fill-opacity': fillOpacity,
}
return {
...polygonSet,
style,
}
})
// polygonSets = [polygonSets[0]]
// polygonSets = [polygonSets[1]]
// console.log('polygonSets', cloneDeep(polygonSets))
}
//returnGeojson
let r = polygonSets
if (returnGeojson) {
let features = map(polygonSets, (v) => {
//coordinates
let coordinates = v.latLngs
if (inverseCoordinate) {
coordinates = invCoordMultiPolygon(v.latLngs)
}
//o
let o = {
type: 'Feature',
properties: {
range: v.range.text,
style: v.style,
},
geometry: {
type: 'MultiPolygon',
coordinates,
}
}
return o
})
r = {
type: 'FeatureCollection',
features,
}
}
return r
}
export default calcContours