| 1 | import {Adder} from "d3-array";
|
|---|
| 2 | import {asin, atan2, cos, degrees, epsilon, epsilon2, hypot, radians, sin, sqrt} from "./math.js";
|
|---|
| 3 | import noop from "./noop.js";
|
|---|
| 4 | import stream from "./stream.js";
|
|---|
| 5 |
|
|---|
| 6 | var W0, W1,
|
|---|
| 7 | X0, Y0, Z0,
|
|---|
| 8 | X1, Y1, Z1,
|
|---|
| 9 | X2, Y2, Z2,
|
|---|
| 10 | lambda00, phi00, // first point
|
|---|
| 11 | x0, y0, z0; // previous point
|
|---|
| 12 |
|
|---|
| 13 | var centroidStream = {
|
|---|
| 14 | sphere: noop,
|
|---|
| 15 | point: centroidPoint,
|
|---|
| 16 | lineStart: centroidLineStart,
|
|---|
| 17 | lineEnd: centroidLineEnd,
|
|---|
| 18 | polygonStart: function() {
|
|---|
| 19 | centroidStream.lineStart = centroidRingStart;
|
|---|
| 20 | centroidStream.lineEnd = centroidRingEnd;
|
|---|
| 21 | },
|
|---|
| 22 | polygonEnd: function() {
|
|---|
| 23 | centroidStream.lineStart = centroidLineStart;
|
|---|
| 24 | centroidStream.lineEnd = centroidLineEnd;
|
|---|
| 25 | }
|
|---|
| 26 | };
|
|---|
| 27 |
|
|---|
| 28 | // Arithmetic mean of Cartesian vectors.
|
|---|
| 29 | function centroidPoint(lambda, phi) {
|
|---|
| 30 | lambda *= radians, phi *= radians;
|
|---|
| 31 | var cosPhi = cos(phi);
|
|---|
| 32 | centroidPointCartesian(cosPhi * cos(lambda), cosPhi * sin(lambda), sin(phi));
|
|---|
| 33 | }
|
|---|
| 34 |
|
|---|
| 35 | function centroidPointCartesian(x, y, z) {
|
|---|
| 36 | ++W0;
|
|---|
| 37 | X0 += (x - X0) / W0;
|
|---|
| 38 | Y0 += (y - Y0) / W0;
|
|---|
| 39 | Z0 += (z - Z0) / W0;
|
|---|
| 40 | }
|
|---|
| 41 |
|
|---|
| 42 | function centroidLineStart() {
|
|---|
| 43 | centroidStream.point = centroidLinePointFirst;
|
|---|
| 44 | }
|
|---|
| 45 |
|
|---|
| 46 | function centroidLinePointFirst(lambda, phi) {
|
|---|
| 47 | lambda *= radians, phi *= radians;
|
|---|
| 48 | var cosPhi = cos(phi);
|
|---|
| 49 | x0 = cosPhi * cos(lambda);
|
|---|
| 50 | y0 = cosPhi * sin(lambda);
|
|---|
| 51 | z0 = sin(phi);
|
|---|
| 52 | centroidStream.point = centroidLinePoint;
|
|---|
| 53 | centroidPointCartesian(x0, y0, z0);
|
|---|
| 54 | }
|
|---|
| 55 |
|
|---|
| 56 | function centroidLinePoint(lambda, phi) {
|
|---|
| 57 | lambda *= radians, phi *= radians;
|
|---|
| 58 | var cosPhi = cos(phi),
|
|---|
| 59 | x = cosPhi * cos(lambda),
|
|---|
| 60 | y = cosPhi * sin(lambda),
|
|---|
| 61 | z = sin(phi),
|
|---|
| 62 | w = atan2(sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z);
|
|---|
| 63 | W1 += w;
|
|---|
| 64 | X1 += w * (x0 + (x0 = x));
|
|---|
| 65 | Y1 += w * (y0 + (y0 = y));
|
|---|
| 66 | Z1 += w * (z0 + (z0 = z));
|
|---|
| 67 | centroidPointCartesian(x0, y0, z0);
|
|---|
| 68 | }
|
|---|
| 69 |
|
|---|
| 70 | function centroidLineEnd() {
|
|---|
| 71 | centroidStream.point = centroidPoint;
|
|---|
| 72 | }
|
|---|
| 73 |
|
|---|
| 74 | // See J. E. Brock, The Inertia Tensor for a Spherical Triangle,
|
|---|
| 75 | // J. Applied Mechanics 42, 239 (1975).
|
|---|
| 76 | function centroidRingStart() {
|
|---|
| 77 | centroidStream.point = centroidRingPointFirst;
|
|---|
| 78 | }
|
|---|
| 79 |
|
|---|
| 80 | function centroidRingEnd() {
|
|---|
| 81 | centroidRingPoint(lambda00, phi00);
|
|---|
| 82 | centroidStream.point = centroidPoint;
|
|---|
| 83 | }
|
|---|
| 84 |
|
|---|
| 85 | function centroidRingPointFirst(lambda, phi) {
|
|---|
| 86 | lambda00 = lambda, phi00 = phi;
|
|---|
| 87 | lambda *= radians, phi *= radians;
|
|---|
| 88 | centroidStream.point = centroidRingPoint;
|
|---|
| 89 | var cosPhi = cos(phi);
|
|---|
| 90 | x0 = cosPhi * cos(lambda);
|
|---|
| 91 | y0 = cosPhi * sin(lambda);
|
|---|
| 92 | z0 = sin(phi);
|
|---|
| 93 | centroidPointCartesian(x0, y0, z0);
|
|---|
| 94 | }
|
|---|
| 95 |
|
|---|
| 96 | function centroidRingPoint(lambda, phi) {
|
|---|
| 97 | lambda *= radians, phi *= radians;
|
|---|
| 98 | var cosPhi = cos(phi),
|
|---|
| 99 | x = cosPhi * cos(lambda),
|
|---|
| 100 | y = cosPhi * sin(lambda),
|
|---|
| 101 | z = sin(phi),
|
|---|
| 102 | cx = y0 * z - z0 * y,
|
|---|
| 103 | cy = z0 * x - x0 * z,
|
|---|
| 104 | cz = x0 * y - y0 * x,
|
|---|
| 105 | m = hypot(cx, cy, cz),
|
|---|
| 106 | w = asin(m), // line weight = angle
|
|---|
| 107 | v = m && -w / m; // area weight multiplier
|
|---|
| 108 | X2.add(v * cx);
|
|---|
| 109 | Y2.add(v * cy);
|
|---|
| 110 | Z2.add(v * cz);
|
|---|
| 111 | W1 += w;
|
|---|
| 112 | X1 += w * (x0 + (x0 = x));
|
|---|
| 113 | Y1 += w * (y0 + (y0 = y));
|
|---|
| 114 | Z1 += w * (z0 + (z0 = z));
|
|---|
| 115 | centroidPointCartesian(x0, y0, z0);
|
|---|
| 116 | }
|
|---|
| 117 |
|
|---|
| 118 | export default function(object) {
|
|---|
| 119 | W0 = W1 =
|
|---|
| 120 | X0 = Y0 = Z0 =
|
|---|
| 121 | X1 = Y1 = Z1 = 0;
|
|---|
| 122 | X2 = new Adder();
|
|---|
| 123 | Y2 = new Adder();
|
|---|
| 124 | Z2 = new Adder();
|
|---|
| 125 | stream(object, centroidStream);
|
|---|
| 126 |
|
|---|
| 127 | var x = +X2,
|
|---|
| 128 | y = +Y2,
|
|---|
| 129 | z = +Z2,
|
|---|
| 130 | m = hypot(x, y, z);
|
|---|
| 131 |
|
|---|
| 132 | // If the area-weighted ccentroid is undefined, fall back to length-weighted ccentroid.
|
|---|
| 133 | if (m < epsilon2) {
|
|---|
| 134 | x = X1, y = Y1, z = Z1;
|
|---|
| 135 | // If the feature has zero length, fall back to arithmetic mean of point vectors.
|
|---|
| 136 | if (W1 < epsilon) x = X0, y = Y0, z = Z0;
|
|---|
| 137 | m = hypot(x, y, z);
|
|---|
| 138 | // If the feature still has an undefined ccentroid, then return.
|
|---|
| 139 | if (m < epsilon2) return [NaN, NaN];
|
|---|
| 140 | }
|
|---|
| 141 |
|
|---|
| 142 | return [atan2(y, x) * degrees, asin(z / m) * degrees];
|
|---|
| 143 | }
|
|---|