Skip to content

Commit ae83209

Browse files
committed
Merge branch 'fix-centroid' into 3.2
2 parents 12875f4 + 8a96498 commit ae83209

4 files changed

Lines changed: 98 additions & 76 deletions

File tree

d3.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2285,9 +2285,7 @@ d3 = function() {
22852285
d3_geo_centroidDimension = d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
22862286
d3.geo.stream(object, d3_geo_centroid);
22872287
var m;
2288-
if (d3_geo_centroidW && Math.abs(m = Math.sqrt(d3_geo_centroidX * d3_geo_centroidX + d3_geo_centroidY * d3_geo_centroidY + d3_geo_centroidZ * d3_geo_centroidZ)) > ε) {
2289-
return [ Math.atan2(d3_geo_centroidY, d3_geo_centroidX) * d3_degrees, Math.asin(Math.max(-1, Math.min(1, d3_geo_centroidZ / m))) * d3_degrees ];
2290-
}
2288+
return d3_geo_centroidW && Math.abs(m = Math.sqrt(d3_geo_centroidX * d3_geo_centroidX + d3_geo_centroidY * d3_geo_centroidY + d3_geo_centroidZ * d3_geo_centroidZ)) > ε ? [ Math.atan2(d3_geo_centroidY, d3_geo_centroidX) * d3_degrees, Math.asin(Math.max(-1, Math.min(1, d3_geo_centroidZ / m))) * d3_degrees ] : [ NaN, NaN ];
22912289
};
22922290
var d3_geo_centroidDimension, d3_geo_centroidW, d3_geo_centroidX, d3_geo_centroidY, d3_geo_centroidZ;
22932291
var d3_geo_centroid = {
@@ -3944,7 +3942,8 @@ d3 = function() {
39443942
}
39453943
function d3_svg_lineStep(points) {
39463944
var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
3947-
while (++i < n) path.push("H", (p[0] + (p = points[i])[0]) / 2, "V", p[1], "H", p[0]);
3945+
while (++i < n) path.push("H", (p[0] + (p = points[i])[0]) / 2, "V", p[1]);
3946+
if (n > 1) path.push("H", p[0]);
39483947
return path.join("");
39493948
}
39503949
function d3_svg_lineStepBefore(points) {

d3.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/geo/centroid.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,9 @@ d3.geo.centroid = function(object) {
66
d3_geo_centroidDimension = d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
77
d3.geo.stream(object, d3_geo_centroid);
88
var m;
9-
if (d3_geo_centroidW &&
10-
Math.abs(m = Math.sqrt(d3_geo_centroidX * d3_geo_centroidX + d3_geo_centroidY * d3_geo_centroidY + d3_geo_centroidZ * d3_geo_centroidZ)) > ε) {
11-
return [
12-
Math.atan2(d3_geo_centroidY, d3_geo_centroidX) * d3_degrees,
13-
Math.asin(Math.max(-1, Math.min(1, d3_geo_centroidZ / m))) * d3_degrees
14-
];
15-
}
9+
return d3_geo_centroidW && Math.abs(m = Math.sqrt(d3_geo_centroidX * d3_geo_centroidX + d3_geo_centroidY * d3_geo_centroidY + d3_geo_centroidZ * d3_geo_centroidZ)) > ε
10+
? [Math.atan2(d3_geo_centroidY, d3_geo_centroidX) * d3_degrees, Math.asin(Math.max(-1, Math.min(1, d3_geo_centroidZ / m))) * d3_degrees]
11+
: [NaN, NaN];
1612
};
1713

1814
var d3_geo_centroidDimension,

test/geo/centroid-test.js

Lines changed: 90 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,31 @@ var suite = vows.describe("d3.geo.centroid");
88
suite.addBatch({
99
"centroid": {
1010
topic: load("geo/centroid").expression("d3.geo.centroid"),
11-
"Point": function(centroid) {
12-
assert.deepEqual(centroid({type: "Point", coordinates: [0, 0]}), [0, 0]);
13-
},
14-
"MultiPoint": {
15-
"": function(centroid) {
16-
assert.inDelta(centroid({type: "MultiPoint", coordinates: [[0, 0], [1, 2]]}), [0.499847, 1.000038], 1e-6);
17-
},
18-
"antimeridian": function(centroid) {
19-
assert.deepEqual(centroid({type: "MultiPoint", coordinates: [[179, 0], [-179, 0]]}), [180, 0]);
20-
},
21-
"rings": {
22-
"equator": function(centroid) {
23-
assert.isUndefined(centroid({type: "MultiPoint", coordinates: [[0, 0], [90, 0], [180, 0], [-90, 0]]}));
24-
},
25-
"polar": function(centroid) {
26-
assert.isUndefined(centroid({type: "MultiPoint", coordinates: [[0, 0], [0, 90], [180, 0], [0, -90]]}));
27-
}
28-
}
29-
},
30-
"LineString": function(centroid) {
11+
12+
"the centroid of a point is itself": function(centroid) {
13+
assert.inDelta(centroid({type: "Point", coordinates: [0, 0]}), [0, 0], 1e-6);
14+
assert.inDelta(centroid({type: "Point", coordinates: [1, 1]}), [1, 1], 1e-6);
15+
assert.inDelta(centroid({type: "Point", coordinates: [2, 3]}), [2, 3], 1e-6);
16+
assert.inDelta(centroid({type: "Point", coordinates: [-4, -5]}), [-4, -5], 1e-6);
17+
},
18+
19+
"the centroid of a set of points is the (spherical) average of its constituent members": function(centroid) {
20+
assert.inDelta(centroid({type: "GeometryCollection", geometries: [{type: "Point", coordinates: [0, 0]}, {type: "Point", coordinates: [1, 2]}]}), [0.499847, 1.000038], 1e-6);
21+
assert.inDelta(centroid({type: "MultiPoint", coordinates: [[0, 0], [1, 2]]}), [0.499847, 1.000038], 1e-6);
22+
assert.inDelta(centroid({type: "MultiPoint", coordinates: [[179, 0], [-179, 0]]}), [180, 0], 1e-6);
23+
},
24+
25+
"the centroid of a set of points and their antipodes is ambiguous": function(centroid) {
26+
assert.ok(centroid({type: "MultiPoint", coordinates: [[0, 0], [180, 0]]}).every(isNaN));
27+
assert.ok(centroid({type: "MultiPoint", coordinates: [[0, 0], [90, 0], [180, 0], [-90, 0]]}).every(isNaN));
28+
assert.ok(centroid({type: "MultiPoint", coordinates: [[0, 0], [0, 90], [180, 0], [0, -90]]}).every(isNaN));
29+
},
30+
31+
"the centroid of the empty set of points is ambiguous": function(centroid) {
32+
assert.ok(centroid({type: "MultiPoint", coordinates: []}).every(isNaN));
33+
},
34+
35+
"the centroid of a line string is the (spherical) average of its constituent great arc segments": function(centroid) {
3136
assert.inDelta(centroid({type: "LineString", coordinates: [[0, 0], [1, 0]]}), [.5, 0], 1e-6);
3237
assert.inDelta(centroid({type: "LineString", coordinates: [[0, 0], [0, 90]]}), [0, 45], 1e-6);
3338
assert.inDelta(centroid({type: "LineString", coordinates: [[0, 0], [0, 45], [0, 90]]}), [0, 45], 1e-6);
@@ -36,65 +41,87 @@ suite.addBatch({
3641
assert.inDelta(centroid({type: "LineString", coordinates: [[179, -1], [-179, 1]]}), [180, 0], 1e-6);
3742
assert.inDelta(centroid({type: "LineString", coordinates: [[-179, 0], [0, 0], [179, 0]]}), [0, 0], 1e-6);
3843
assert.inDelta(centroid({type: "LineString", coordinates: [[-180, -90], [0, 0], [0, 90]]}), [0, 0], 1e-6);
39-
assert.isUndefined(centroid({type: "LineString", coordinates: [[0, -90], [0, 90]]}));
4044
},
41-
"MultiLineString": function(centroid) {
45+
46+
"the centroid of a great arc from a point to its antipode is ambiguous": function(centroid) {
47+
assert.ok(centroid({type: "LineString", coordinates: [[180, 0], [0, 0]]}).every(isNaN));
48+
assert.ok(centroid({type: "MultiLineString", coordinates: [[[0, -90], [0, 90]]]}).every(isNaN));
49+
},
50+
51+
"the centroid of a set of line strings is the (spherical) average of its constituent great arc segments": function(centroid) {
4252
assert.inDelta(centroid({type: "MultiLineString", coordinates: [[[0, 0], [0, 2]]]}), [0, 1], 1e-6);
4353
},
44-
"Polygon": function(centroid) {
54+
55+
"an empty line is treated as a point": function(centroid) {
56+
assert.inDelta(centroid({type: "LineString", coordinates: [[1, 1], [1, 1]]}), [1, 1], 1e-6); // TODO
57+
assert.inDelta(centroid({type: "GeometryCollection", geometries: [{type: "Point", coordinates: [0, 0]}, {type: "LineString", coordinates: [[1, 2], [1, 2]]}]}), [0.499847, 1.000038], 1e-6);
58+
},
59+
60+
"an empty polygon is treated as a point": function(centroid) {
61+
assert.inDelta(centroid({type: "Polygon", coordinates: [[[1, 1], [1, 1], [1, 1], [1, 1]]]}), [1, 1], 1e-6); // TODO
62+
assert.inDelta(centroid({type: "GeometryCollection", geometries: [{type: "Point", coordinates: [0, 0]}, {type: "Polygon", coordinates: [[[1, 2], [1, 2], [1, 2], [1, 2]]]}]}), [0.499847, 1.000038], 1e-6);
63+
},
64+
65+
// TODO Don’t treat a polygon as a line string.
66+
"the centroid of a polygon is the (spherical) average of its surface": function(centroid) {
4567
assert.inDelta(centroid({type: "Polygon", coordinates: [[[0, -90], [0, 0], [0, 90], [1, 0], [0, -90]]]}), [.5, 0], 1e-6);
4668
assert.inDelta(centroid(_.geo.circle().angle(5).origin([0, 45])()), [0, 45], 1e-6);
47-
assert.equal(centroid({type: "Polygon", coordinates: [_.range(-180, 180 + 1 / 2, 1).map(function(x) { return [x, -60]; })]})[1], -90);
69+
assert.inDelta(centroid({type: "Polygon", coordinates: [_.range(-180, 180 + 1 / 2, 1).map(function(x) { return [x, -60]; })]})[1], -90, 1e-6);
4870
assert.inDelta(centroid({type: "Polygon", coordinates: [[[0, -10], [0, 10], [10, 10], [10, -10], [0, -10]]]}), [5, 0], 1e-6);
4971
},
50-
"MultiPolygon": function(centroid) {
72+
73+
// TODO Actually test multiple polygons here.
74+
"the centroid of a set of polygons is the (spherical) average of its surface": function(centroid) {
5175
assert.inDelta(centroid({type: "MultiPolygon", coordinates: [[[[0, -90], [0, 0], [0, 90], [1, 0], [0, -90]]]]}), [.5, 0], 1e-6);
76+
assert.inDelta(centroid({type: "GeometryCollection", geometries: [{type: "Polygon", coordinates: [[[0, -90], [0, 0], [0, 90], [1, 0], [0, -90]]]}]}), [.5, 0], 1e-6);
5277
},
53-
"Sphere": function(centroid) {
54-
assert.isUndefined(centroid({type: "Sphere"}));
78+
79+
"the centroid of a sphere is ambiguous": function(centroid) {
80+
assert.ok(centroid({type: "Sphere"}).every(isNaN));
5581
},
56-
"Feature": function(centroid) {
57-
assert.deepEqual(centroid({type: "Feature", geometry: {type: "Point", coordinates: [0, 0]}}), [0, 0]);
82+
83+
"the centroid of a feature is the centroid of its constituent geometry": function(centroid) {
84+
assert.inDelta(centroid({type: "Feature", geometry: {type: "LineString", coordinates: [[1, 1], [1, 1]]}}), [1, 1], 1e-6); // TODO
85+
assert.inDelta(centroid({type: "Feature", geometry: {type: "Point", coordinates: [1, 1]}}), [1, 1], 1e-6);
86+
assert.inDelta(centroid({type: "Feature", geometry: {type: "Polygon", coordinates: [[[0, -90], [0, 0], [0, 90], [1, 0], [0, -90]]]}}), [.5, 0], 1e-6);
5887
},
59-
"FeatureCollection": function(centroid) {
88+
89+
"the centroid of a feature collection is the centroid of its constituent geometry": function(centroid) {
6090
assert.inDelta(centroid({type: "FeatureCollection", features: [
6191
{type: "Feature", geometry: {type: "LineString", coordinates: [[179, 0], [180, 0]]}},
6292
{type: "Feature", geometry: {type: "Point", coordinates: [0, 0]}}
6393
]}), [179.5, 0], 1e-6);
6494
},
65-
"GeometryCollection": {
66-
"LineString, Point": function(centroid) {
67-
assert.inDelta(centroid({type: "GeometryCollection", geometries: [
68-
{type: "LineString", coordinates: [[179, 0], [180, 0]]},
69-
{type: "Point", coordinates: [0, 0]}
70-
]}), [179.5, 0], 1e-6);
71-
},
72-
"Polygon, LineString, Point": function(centroid) {
73-
assert.inDelta(centroid({type: "GeometryCollection", geometries: [
74-
{type: "Polygon", coordinates: [[[-180, 0], [-180, 1], [-179, 1], [-179, 0], [-180, 0]]]},
75-
{type: "LineString", coordinates: [[179, 0], [180, 0]]},
76-
{type: "Point", coordinates: [0, 0]}
77-
]}), [-179.5, 0.5], 1e-6);
78-
},
79-
"Point, LineString, Polygon": function(centroid) {
80-
assert.inDelta(centroid({type: "GeometryCollection", geometries: [
81-
{type: "Point", coordinates: [0, 0]},
82-
{type: "LineString", coordinates: [[179, 0], [180, 0]]},
83-
{type: "Polygon", coordinates: [[[-180, 0], [-180, 1], [-179, 1], [-179, 0], [-180, 0]]]}
84-
]}), [-179.5, 0.5], 1e-6);
85-
},
86-
"Sphere, Point": function(centroid) {
87-
assert.isUndefined(centroid({type: "GeometryCollection", geometries: [
88-
{type: "Sphere"},
89-
{type: "Point", coordinates: [0, 0]}
90-
]}));
91-
},
92-
"Point, Sphere": function(centroid) {
93-
assert.isUndefined(centroid({type: "GeometryCollection", geometries: [
94-
{type: "Point", coordinates: [0, 0]},
95-
{type: "Sphere"}
96-
]}));
97-
}
95+
96+
"the centroid of a non-empty line string and a point only considers the line string": function(centroid) {
97+
assert.inDelta(centroid({type: "GeometryCollection", geometries: [
98+
{type: "LineString", coordinates: [[179, 0], [180, 0]]},
99+
{type: "Point", coordinates: [0, 0]}
100+
]}), [179.5, 0], 1e-6);
101+
},
102+
103+
"the centroid of a non-empty polygon, a non-empty line string and a point only considers the polygon": function(centroid) {
104+
assert.inDelta(centroid({type: "GeometryCollection", geometries: [
105+
{type: "Polygon", coordinates: [[[-180, 0], [-180, 1], [-179, 1], [-179, 0], [-180, 0]]]},
106+
{type: "LineString", coordinates: [[179, 0], [180, 0]]},
107+
{type: "Point", coordinates: [0, 0]}
108+
]}), [-179.5, 0.5], 1e-6);
109+
assert.inDelta(centroid({type: "GeometryCollection", geometries: [
110+
{type: "Point", coordinates: [0, 0]},
111+
{type: "LineString", coordinates: [[179, 0], [180, 0]]},
112+
{type: "Polygon", coordinates: [[[-180, 0], [-180, 1], [-179, 1], [-179, 0], [-180, 0]]]}
113+
]}), [-179.5, 0.5], 1e-6);
114+
},
115+
116+
"the centroid of the sphere and a point only considers the sphere": function(centroid) {
117+
assert.ok(centroid({type: "GeometryCollection", geometries: [
118+
{type: "Sphere"},
119+
{type: "Point", coordinates: [0, 0]}
120+
]}).every(isNaN));
121+
assert.ok(centroid({type: "GeometryCollection", geometries: [
122+
{type: "Point", coordinates: [0, 0]},
123+
{type: "Sphere"}
124+
]}).every(isNaN));
98125
}
99126
}
100127
});

0 commit comments

Comments
 (0)