{"version":3,"sources":["../../index.ts"],"sourcesContent":["import {\n FeatureCollection,\n Feature,\n Position,\n Polygon,\n GeoJsonProperties,\n Point,\n} from \"geojson\";\nimport { coordAll, featureEach } from \"@turf/meta\";\nimport { getCoords } from \"@turf/invariant\";\nimport { featureCollection, isObject, isNumber } from \"@turf/helpers\";\nimport { centerMean } from \"@turf/center-mean\";\nimport { pointsWithinPolygon } from \"@turf/points-within-polygon\";\nimport { ellipse } from \"@turf/ellipse\";\n\ndeclare interface SDEProps {\n meanCenterCoordinates: Position;\n semiMajorAxis: number;\n semiMinorAxis: number;\n numberOfFeatures: number;\n angle: number;\n percentageWithinEllipse: number;\n}\n\ndeclare interface StandardDeviationalEllipse extends Feature {\n properties: {\n standardDeviationalEllipse: SDEProps;\n [key: string]: any;\n } | null;\n}\n\n/**\n * Takes a collection of features and returns a standard deviational ellipse,\n * also known as a “directional distribution.” The standard deviational ellipse\n * aims to show the direction and the distribution of a dataset by drawing\n * an ellipse that contains about one standard deviation’s worth (~ 70%) of the\n * data.\n *\n * This module mirrors the functionality of {@link http://desktop.arcgis.com/en/arcmap/10.3/tools/spatial-statistics-toolbox/directional-distribution.htm|Directional Distribution}\n * in ArcGIS and the {@link http://arken.nmbu.no/~havatv/gis/qgisplugins/SDEllipse/|QGIS Standard Deviational Ellipse Plugin}\n *\n * **Bibliography**\n *\n * • Robert S. Yuill, “The Standard Deviational Ellipse; An Updated Tool for\n * Spatial Description,” _Geografiska Annaler_ 53, no. 1 (1971): 28–39,\n * doi:{@link https://doi.org/10.2307/490885|10.2307/490885}.\n *\n * • Paul Hanly Furfey, “A Note on Lefever’s “Standard Deviational Ellipse,”\n * _American Journal of Sociology_ 33, no. 1 (1927): 94—98,\n * doi:{@link https://doi.org/10.1086/214336|10.1086/214336}.\n *\n *\n * @function\n * @param {FeatureCollection} points GeoJSON points\n * @param {Object} [options={}] Optional parameters\n * @param {string} [options.weight] the property name used to weight the center\n * @param {number} [options.steps=64] number of steps for the polygon\n * @param {Object} [options.properties={}] properties to pass to the resulting ellipse\n * @returns {Feature} an elliptical Polygon that includes approximately 1 SD of the dataset within it.\n * @example\n *\n * const bbox = [-74, 40.72, -73.98, 40.74];\n * const points = turf.randomPoint(400, {bbox: bbox});\n * const sdEllipse = turf.standardDeviationalEllipse(points);\n *\n * //addToMap\n * const addToMap = [points, sdEllipse];\n *\n */\nfunction standardDeviationalEllipse(\n points: FeatureCollection,\n options?: {\n properties?: GeoJsonProperties;\n weight?: string;\n steps?: number;\n }\n): StandardDeviationalEllipse {\n // Optional params\n options = options || {};\n if (!isObject(options)) throw new Error(\"options is invalid\");\n const steps = options.steps || 64;\n const weightTerm = options.weight;\n const properties = options.properties || {};\n\n // Validation:\n if (!isNumber(steps)) throw new Error(\"steps must be a number\");\n if (!isObject(properties)) throw new Error(\"properties must be a number\");\n\n // Calculate mean center & number of features:\n const numberOfFeatures = coordAll(points).length;\n const meanCenter = centerMean(points, { weight: weightTerm });\n\n // Calculate angle of rotation:\n // [X, Y] = mean center of all [x, y].\n // theta = arctan( (A + B) / C )\n // A = sum((x - X)^2) - sum((y - Y)^2)\n // B = sqrt(A^2 + 4(sum((x - X)(y - Y))^2))\n // C = 2(sum((x - X)(y - Y)))\n\n let xDeviationSquaredSum = 0;\n let yDeviationSquaredSum = 0;\n let xyDeviationSum = 0;\n\n featureEach(points, function (point) {\n // weightTerm or point.properties might be undefined, hence this check.\n const weight = weightTerm ? point.properties?.[weightTerm] || 1 : 1;\n const deviation = getDeviations(getCoords(point), getCoords(meanCenter));\n xDeviationSquaredSum += Math.pow(deviation.x, 2) * weight;\n yDeviationSquaredSum += Math.pow(deviation.y, 2) * weight;\n xyDeviationSum += deviation.x * deviation.y * weight;\n });\n\n const bigA = xDeviationSquaredSum - yDeviationSquaredSum;\n const bigB = Math.sqrt(Math.pow(bigA, 2) + 4 * Math.pow(xyDeviationSum, 2));\n const bigC = 2 * xyDeviationSum;\n const theta = Math.atan((bigA + bigB) / bigC);\n const thetaDeg = (theta * 180) / Math.PI;\n\n // Calculate axes:\n // sigmaX = sqrt((1 / n - 2) * sum((((x - X) * cos(theta)) - ((y - Y) * sin(theta)))^2))\n // sigmaY = sqrt((1 / n - 2) * sum((((x - X) * sin(theta)) - ((y - Y) * cos(theta)))^2))\n let sigmaXsum = 0;\n let sigmaYsum = 0;\n let weightsum = 0;\n featureEach(points, function (point) {\n // weightTerm or point.properties might be undefined, hence this check.\n const weight = weightTerm ? point.properties?.[weightTerm] || 1 : 1;\n const deviation = getDeviations(getCoords(point), getCoords(meanCenter));\n sigmaXsum +=\n Math.pow(\n deviation.x * Math.cos(theta) - deviation.y * Math.sin(theta),\n 2\n ) * weight;\n sigmaYsum +=\n Math.pow(\n deviation.x * Math.sin(theta) + deviation.y * Math.cos(theta),\n 2\n ) * weight;\n weightsum += weight;\n });\n\n const sigmaX = Math.sqrt((2 * sigmaXsum) / weightsum);\n const sigmaY = Math.sqrt((2 * sigmaYsum) / weightsum);\n\n const theEllipse: Feature = ellipse(meanCenter, sigmaX, sigmaY, {\n units: \"degrees\",\n angle: thetaDeg,\n steps: steps,\n properties: properties,\n });\n const pointsWithinEllipse = pointsWithinPolygon(\n points,\n featureCollection([theEllipse])\n );\n const standardDeviationalEllipseProperties = {\n meanCenterCoordinates: getCoords(meanCenter),\n semiMajorAxis: sigmaX,\n semiMinorAxis: sigmaY,\n numberOfFeatures: numberOfFeatures,\n angle: thetaDeg,\n percentageWithinEllipse:\n (100 * coordAll(pointsWithinEllipse).length) / numberOfFeatures,\n };\n // Make sure properties object exists.\n theEllipse.properties = theEllipse.properties ?? {};\n theEllipse.properties.standardDeviationalEllipse =\n standardDeviationalEllipseProperties;\n\n // We have added the StandardDeviationalEllipse specific properties, so\n // confirm this to Typescript with a cast.\n return theEllipse as StandardDeviationalEllipse;\n}\n\n/**\n * Get x_i - X and y_i - Y\n *\n * @private\n * @param {Position} coordinates Array of [x_i, y_i]\n * @param {Position} center Array of [X, Y]\n * @returns {Object} { x: n, y: m }\n */\nfunction getDeviations(coordinates: Position, center: Position) {\n return {\n x: coordinates[0] - center[0],\n y: coordinates[1] - center[1],\n };\n}\n\nexport { standardDeviationalEllipse, SDEProps, StandardDeviationalEllipse };\nexport default standardDeviationalEllipse;\n"],"mappings":"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","names":["_a"]}