{"version":3,"sources":["../../index.ts"],"sourcesContent":["import { BBox, FeatureCollection, Point } from \"geojson\";\nimport { area } from \"@turf/area\";\nimport { bbox as turfBBox } from \"@turf/bbox\";\nimport { bboxPolygon } from \"@turf/bbox-polygon\";\nimport { getCoord } from \"@turf/invariant\";\nimport { squareGrid } from \"@turf/square-grid\";\n\ninterface QuadratAnalysisResult {\n criticalValue: number;\n maxAbsoluteDifference: number;\n isRandom: boolean;\n observedDistribution: number[];\n}\n\n/**\n * Quadrat analysis lays a set of equal-size areas(quadrat) over the study area and counts\n * the number of features in each quadrat and creates a frequency table.\n * The table lists the number of quadrats containing no features,\n * the number containing one feature, two features, and so on,\n * all the way up to the quadrat containing the most features.\n * The method then creates the frequency table for the random distribution, usually based on a Poisson distribution.\n * The method uses the distribution to calculate the probability for 0 feature occuring,\n * 1 feature occuring, 2 features, and so on,\n * and lists these probabilities in the frequency table.\n * By comparing the two frequency tables, you can see whether the features create a pattern.\n * If the table for the observed distribution has more quadrats containing many features than the\n * table for the random distribution dose, then the features create a clustered pattern.\n *\n * It is hard to judge the frequency tables are similar or different just by looking at them.\n * So, we can use serval statistical tests to find out how much the frequency tables differ.\n * We use Kolmogorov-Smirnov test.This method calculates cumulative probabilities for both distributions,\n * and then compares the cumulative probabilities at each class level and selects the largest absolute difference D.\n * Then, the test compares D to the critical value for a confidence level you specify.\n * If D is greater than the critical value, the difference between the observed distribution and\n * the random distribution is significant. The greater the value the bigger the difference.\n *\n * Traditionally, squares are used for the shape of the quadrats, in a regular grid(square-grid).\n * Some researchers suggest that the quadrat size equal twice the size of mean area per feature,\n * which is simply the area of the study area divided by the number of features.\n *\n *\n * @function\n * @param {FeatureCollection} pointFeatureSet point set to study\n * @param {Object} [options={}] optional parameters\n * @param {[number, number, number, number]} [options.studyBbox] bbox representing the study area\n * @param {20 | 15 | 10 | 5 | 2 | 1} [options.confidenceLevel=20] a confidence level.\n * The unit is percentage . 5 means 95%, value must be in {@link K_TABLE}\n * @returns {QuadratAnalysisResult} result\n * @example\n *\n * var bbox = [-65, 40, -63, 42];\n * var dataset = turf.randomPoint(100, { bbox: bbox });\n * var result = turf.quadratAnalysis(dataset);\n *\n */\nfunction quadratAnalysis(\n pointFeatureSet: FeatureCollection,\n options: {\n studyBbox?: [number, number, number, number];\n confidenceLevel?: 20 | 15 | 10 | 5 | 2 | 1;\n }\n): QuadratAnalysisResult {\n options = options || {};\n const studyBbox = options.studyBbox || turfBBox(pointFeatureSet);\n const confidenceLevel = options.confidenceLevel || 20;\n const points = pointFeatureSet.features;\n\n // create square-grid\n const numOfPoints = points.length;\n const sizeOfArea = area(bboxPolygon(studyBbox));\n const lengthOfSide = Math.sqrt((sizeOfArea / numOfPoints) * 2);\n const grid = squareGrid(studyBbox, lengthOfSide, {\n units: \"meters\",\n });\n const quadrats = grid.features;\n\n // count the number of features in each quadrat\n const quadratIdDict: { [key: string]: { box: BBox; cnt: number } } = {};\n for (let i = 0; i < quadrats.length; i++) {\n quadratIdDict[i] = {\n box: turfBBox(quadrats[i]),\n cnt: 0,\n };\n }\n\n let sumOfPoint = 0;\n for (const pt of points) {\n for (const key of Object.keys(quadratIdDict)) {\n const box = quadratIdDict[key].box;\n if (inBBox(getCoord(pt), box)) {\n quadratIdDict[key].cnt += 1;\n sumOfPoint += 1;\n break;\n }\n }\n }\n\n // the most amount of features in quadrat\n let maxCnt = 0;\n for (const key of Object.keys(quadratIdDict)) {\n const cnt = quadratIdDict[key].cnt;\n if (cnt > maxCnt) {\n maxCnt = cnt;\n }\n }\n\n const expectedDistribution = [];\n const numOfQuadrat = Object.keys(quadratIdDict).length;\n const lambda = sumOfPoint / numOfQuadrat;\n\n // get the cumulative probability of the random distribution\n let cumulativeProbility = 0.0;\n for (let x = 0; x < maxCnt + 1; x++) {\n cumulativeProbility +=\n (Math.exp(-lambda) * Math.pow(lambda, x)) / factorial(x);\n expectedDistribution.push(cumulativeProbility);\n }\n\n // get the cumulative probability of the observed distribution\n const observedDistribution = [];\n let cumulativeObservedQuads = 0;\n for (let x = 0; x < maxCnt + 1; x++) {\n for (const key of Object.keys(quadratIdDict)) {\n if (quadratIdDict[key].cnt === x) {\n cumulativeObservedQuads += 1;\n }\n }\n const p = cumulativeObservedQuads / numOfQuadrat;\n observedDistribution.push(p);\n }\n\n // get the largest absolute difference between two distributions\n let maxDifference = 0;\n for (let x = 0; x < maxCnt + 1; x++) {\n const difference = Math.abs(\n expectedDistribution[x] - observedDistribution[x]\n );\n if (difference > maxDifference) {\n maxDifference = difference;\n }\n }\n\n const k = K_TABLE[confidenceLevel];\n\n // statistical test\n const criticalValue = k / Math.sqrt(numOfQuadrat);\n const result: QuadratAnalysisResult = {\n criticalValue,\n isRandom: true,\n maxAbsoluteDifference: maxDifference,\n observedDistribution,\n };\n\n if (maxDifference > criticalValue) {\n result.isRandom = false;\n }\n\n return result;\n}\n\n/**\n * the confidence level\n *\n * @constant\n * @type {Object}\n * @property {number} 20 1.07275\n * @property {number} 15 1.13795\n * @property {number} 10 1.22385\n * @property {number} 5 1.3581\n * @property {number} 2 1.51743\n * @property {number} 1 1.62762\n */\nconst K_TABLE = {\n 20: 1.07275,\n 15: 1.13795,\n 10: 1.22385,\n 5: 1.3581,\n 2: 1.51743,\n 1: 1.62762,\n};\n\n/**\n * the return type of the quadratAnalysis\n *\n * @typedef {object} QuadratAnalysisResult\n * @property {number} criticalValue\n * @property {number} maxAbsoluteDifference\n * @property {boolean} isRandom\n * @property {Array} observedDistribution the cumulative distribution of observed features,\n * the index represents the number of features in the quadrat.\n */\n\n/**\n * inBBox from @turf/boolean-point-in-polygon\n *\n * @private\n * @param {Array} pt point [x,y]\n * @param {BBox} bbox BBox [west, south, east, north]\n * @returns {boolean} true/false if point is inside BBox\n */\nfunction inBBox(pt: number[], bbox: BBox) {\n return (\n bbox[0] <= pt[0] && bbox[1] <= pt[1] && bbox[2] >= pt[0] && bbox[3] >= pt[1]\n );\n}\n\n/**\n * https://stackoverflow.com/questions/3959211/fast-factorial-function-in-javascript\n * @private\n * @param {number} num Number\n * @returns {number} the factorial of num\n */\nfunction factorial(num: number) {\n const f: number[] = [];\n function inner(n: number): number {\n if (n === 0 || n === 1) {\n return 1;\n }\n if (f[n] > 0) {\n return f[n];\n }\n return (f[n] = inner(n - 1) * n);\n }\n return inner(num);\n}\n\nexport { QuadratAnalysisResult, quadratAnalysis };\nexport default quadratAnalysis;\n"],"mappings":"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","names":[]}