{"version":3,"sources":["../../index.ts"],"sourcesContent":["import { FeatureCollection, Feature, Point, Position } from \"geojson\";\nimport { centerMean } from \"@turf/center-mean\";\nimport { distance } from \"@turf/distance\";\nimport { centroid } from \"@turf/centroid\";\nimport { isNumber, point, isObject, featureCollection } from \"@turf/helpers\";\nimport { featureEach } from \"@turf/meta\";\n\n/**\n * Takes a {@link FeatureCollection} of points and calculates the median center,\n * algorithimically. The median center is understood as the point that is\n * requires the least total travel from all other points.\n *\n * Turfjs has four different functions for calculating the center of a set of\n * data. Each is useful depending on circumstance.\n *\n * `@turf/center` finds the simple center of a dataset, by finding the\n * midpoint between the extents of the data. That is, it divides in half the\n * farthest east and farthest west point as well as the farthest north and\n * farthest south.\n *\n * `@turf/center-of-mass` imagines that the dataset is a sheet of paper.\n * The center of mass is where the sheet would balance on a fingertip.\n *\n * `@turf/center-mean` takes the averages of all the coordinates and\n * produces a value that respects that. Unlike `@turf/center`, it is\n * sensitive to clusters and outliers. It lands in the statistical middle of a\n * dataset, not the geographical. It can also be weighted, meaning certain\n * points are more important than others.\n *\n * `@turf/center-median` takes the mean center and tries to find, iteratively,\n * a new point that requires the least amount of travel from all the points in\n * the dataset. It is not as sensitive to outliers as `@turf/center-mean`, but it is\n * attracted to clustered data. It, too, can be weighted.\n *\n * **Bibliography**\n *\n * Harold W. Kuhn and Robert E. Kuenne, β€œAn Efficient Algorithm for the\n * Numerical Solution of the Generalized Weber Problem in Spatial\n * Economics,” _Journal of Regional Science_ 4, no. 2 (1962): 21–33,\n * doi:{@link https://doi.org/10.1111/j.1467-9787.1962.tb00902.x}.\n *\n * James E. Burt, Gerald M. Barber, and David L. Rigby, _Elementary\n * Statistics for Geographers_, 3rd ed., New York: The Guilford\n * Press, 2009, 150–151.\n *\n * @function\n * @param {FeatureCollection} features Any GeoJSON Feature Collection\n * @param {Object} [options={}] Optional parameters\n * @param {string} [options.weight] the property name used to weight the center\n * @param {number} [options.tolerance=0.001] the difference in distance between candidate medians at which point the algorighim stops iterating.\n * @param {number} [options.counter=10] how many attempts to find the median, should the tolerance be insufficient.\n * @returns {Feature} The median center of the collection\n * @example\n * var points = turf.points([[0, 0], [1, 0], [0, 1], [5, 8]]);\n * var medianCenter = turf.centerMedian(points);\n *\n * //addToMap\n * var addToMap = [points, medianCenter]\n */\nfunction centerMedian(\n features: FeatureCollection,\n options: { weight?: string; tolerance?: number; counter?: number } = {}\n): Feature<\n Point,\n {\n medianCandidates: Array;\n [key: string]: any;\n }\n> {\n // Optional params\n options = options || {};\n if (!isObject(options)) throw new Error(\"options is invalid\");\n var counter = options.counter || 10;\n if (!isNumber(counter)) throw new Error(\"counter must be a number\");\n var weightTerm = options.weight;\n\n // Calculate mean center:\n var meanCenter = centerMean(features, { weight: options.weight });\n\n // Calculate center of every feature:\n var centroids = featureCollection([]);\n featureEach(features, function (feature) {\n centroids.features.push(\n centroid(feature, {\n properties: { weight: feature.properties?.[weightTerm!] },\n })\n );\n });\n\n const properties: MedianProperties = {\n tolerance: options.tolerance,\n medianCandidates: [],\n };\n\n return findMedian(\n meanCenter.geometry.coordinates,\n [0, 0],\n centroids,\n properties,\n counter\n ) as Feature<\n Point,\n {\n medianCandidates: Array;\n [key: string]: any;\n }\n >;\n}\n\ninterface MedianProperties {\n tolerance?: number;\n medianCandidates: Position[];\n}\n\n/**\n * Recursive function to find new candidate medians.\n *\n * @private\n * @param {Position} candidateMedian current candidate median\n * @param {Position} previousCandidate the previous candidate median\n * @param {FeatureCollection} centroids the collection of centroids whose median we are determining\n * @param {number} counter how many attempts to try before quitting.\n * @returns {Feature} the median center of the dataset.\n */\nfunction findMedian(\n candidateMedian: Position,\n previousCandidate: Position,\n centroids: FeatureCollection,\n properties: MedianProperties,\n counter: number\n): Feature {\n var tolerance = properties.tolerance || 0.001;\n var candidateXsum = 0;\n var candidateYsum = 0;\n var kSum = 0;\n var centroidCount = 0;\n featureEach(centroids, function (theCentroid) {\n var weightValue = theCentroid.properties?.weight;\n var weight =\n weightValue === undefined || weightValue === null ? 1 : weightValue;\n weight = Number(weight);\n if (!isNumber(weight)) throw new Error(\"weight value must be a number\");\n if (weight > 0) {\n centroidCount += 1;\n var distanceFromCandidate =\n weight * distance(theCentroid, candidateMedian);\n if (distanceFromCandidate === 0) distanceFromCandidate = 1;\n var k = weight / distanceFromCandidate;\n candidateXsum += theCentroid.geometry.coordinates[0] * k;\n candidateYsum += theCentroid.geometry.coordinates[1] * k;\n kSum += k;\n }\n });\n if (centroidCount < 1) throw new Error(\"no features to measure\");\n var candidateX = candidateXsum / kSum;\n var candidateY = candidateYsum / kSum;\n if (\n centroidCount === 1 ||\n counter === 0 ||\n (Math.abs(candidateX - previousCandidate[0]) < tolerance &&\n Math.abs(candidateY - previousCandidate[1]) < tolerance)\n ) {\n return point([candidateX, candidateY], {\n medianCandidates: properties.medianCandidates,\n });\n } else {\n properties.medianCandidates.push([candidateX, candidateY]);\n return findMedian(\n [candidateX, candidateY],\n candidateMedian,\n centroids,\n properties,\n counter - 1\n );\n }\n}\n\nexport { centerMedian };\nexport default centerMedian;\n"],"mappings":";AACA,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AACzB,SAAS,gBAAgB;AACzB,SAAS,UAAU,OAAO,UAAU,yBAAyB;AAC7D,SAAS,mBAAmB;AAsD5B,SAAS,aACP,UACA,UAAqE,CAAC,GAOtE;AAEA,YAAU,WAAW,CAAC;AACtB,MAAI,CAAC,SAAS,OAAO,EAAG,OAAM,IAAI,MAAM,oBAAoB;AAC5D,MAAI,UAAU,QAAQ,WAAW;AACjC,MAAI,CAAC,SAAS,OAAO,EAAG,OAAM,IAAI,MAAM,0BAA0B;AAClE,MAAI,aAAa,QAAQ;AAGzB,MAAI,aAAa,WAAW,UAAU,EAAE,QAAQ,QAAQ,OAAO,CAAC;AAGhE,MAAI,YAAY,kBAAyB,CAAC,CAAC;AAC3C,cAAY,UAAU,SAAU,SAAS;AAjF3C;AAkFI,cAAU,SAAS;AAAA,MACjB,SAAS,SAAS;AAAA,QAChB,YAAY,EAAE,SAAQ,aAAQ,eAAR,mBAAqB,YAAa;AAAA,MAC1D,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AAED,QAAM,aAA+B;AAAA,IACnC,WAAW,QAAQ;AAAA,IACnB,kBAAkB,CAAC;AAAA,EACrB;AAEA,SAAO;AAAA,IACL,WAAW,SAAS;AAAA,IACpB,CAAC,GAAG,CAAC;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAOF;AAiBA,SAAS,WACP,iBACA,mBACA,WACA,YACA,SACgB;AAChB,MAAI,YAAY,WAAW,aAAa;AACxC,MAAI,gBAAgB;AACpB,MAAI,gBAAgB;AACpB,MAAI,OAAO;AACX,MAAI,gBAAgB;AACpB,cAAY,WAAW,SAAU,aAAa;AAxIhD;AAyII,QAAI,eAAc,iBAAY,eAAZ,mBAAwB;AAC1C,QAAI,SACF,gBAAgB,UAAa,gBAAgB,OAAO,IAAI;AAC1D,aAAS,OAAO,MAAM;AACtB,QAAI,CAAC,SAAS,MAAM,EAAG,OAAM,IAAI,MAAM,+BAA+B;AACtE,QAAI,SAAS,GAAG;AACd,uBAAiB;AACjB,UAAI,wBACF,SAAS,SAAS,aAAa,eAAe;AAChD,UAAI,0BAA0B,EAAG,yBAAwB;AACzD,UAAI,IAAI,SAAS;AACjB,uBAAiB,YAAY,SAAS,YAAY,CAAC,IAAI;AACvD,uBAAiB,YAAY,SAAS,YAAY,CAAC,IAAI;AACvD,cAAQ;AAAA,IACV;AAAA,EACF,CAAC;AACD,MAAI,gBAAgB,EAAG,OAAM,IAAI,MAAM,wBAAwB;AAC/D,MAAI,aAAa,gBAAgB;AACjC,MAAI,aAAa,gBAAgB;AACjC,MACE,kBAAkB,KAClB,YAAY,KACX,KAAK,IAAI,aAAa,kBAAkB,CAAC,CAAC,IAAI,aAC7C,KAAK,IAAI,aAAa,kBAAkB,CAAC,CAAC,IAAI,WAChD;AACA,WAAO,MAAM,CAAC,YAAY,UAAU,GAAG;AAAA,MACrC,kBAAkB,WAAW;AAAA,IAC/B,CAAC;AAAA,EACH,OAAO;AACL,eAAW,iBAAiB,KAAK,CAAC,YAAY,UAAU,CAAC;AACzD,WAAO;AAAA,MACL,CAAC,YAAY,UAAU;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU;AAAA,IACZ;AAAA,EACF;AACF;AAGA,IAAO,6BAAQ;","names":[]}