Skip to content

Commit 1fe9eb5

Browse files
committed
Add antemeridian cutting for all projections.
1 parent d7c2cc7 commit 1fe9eb5

7 files changed

Lines changed: 133 additions & 33 deletions

File tree

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ d3.geo.js: \
185185
src/geo/geo.js \
186186
src/geo/albers-usa.js \
187187
src/geo/albers.js \
188+
src/geo/antemeridian.js \
188189
src/geo/azimuthal-equal-area.js \
189190
src/geo/azimuthal-equidistant.js \
190191
src/geo/bounds.js \

d3.v2.js

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1912,6 +1912,51 @@
19121912
};
19131913
return albers;
19141914
}
1915+
function d3_geo_antemeridianLine(rotate, p) {
1916+
return function(lineString, context) {
1917+
d3_geo_antemeridianClipLine(rotate, lineString, d3_geo_antemeridianContext(p, context));
1918+
};
1919+
}
1920+
function d3_geo_antemeridianRing(rotate, p) {
1921+
return function(ring, context) {
1922+
d3_geo_antemeridianClipLine(rotate, ring, d3_geo_antemeridianContext(p, context));
1923+
context.closePath();
1924+
};
1925+
}
1926+
function d3_geo_antemeridianClipLine(rotate, lineString, context) {
1927+
if (!(n = lineString.length)) return;
1928+
var point = rotate(lineString[0]);
1929+
context.moveTo(λ0 = point[0], φ0 = point[1]);
1930+
for (var λ0, φ0, i = 0, j = 0, n; j < n; j++) {
1931+
point = rotate(lineString[j]);
1932+
var λ1 = point[0], φ1 = point[1], δλ = (Math.abs(λ1 - λ0) + 2 * π) % (2 * π), sλ0 = λ0 > 0;
1933+
if (j > i && sλ0 ^ λ1 > 0 && (δλ >= π || δλ < ε && Math.abs(Math.abs(λ0) - π) < ε)) {
1934+
φ0 = d3_geo_antemeridianIntersect(λ0, φ0, λ1, φ1);
1935+
context.lineTo(sλ0 ? π : -π, φ0);
1936+
context.moveTo(sλ0 ? -π : π, φ0);
1937+
}
1938+
context.lineTo(λ0 = λ1, φ0 = φ1);
1939+
}
1940+
}
1941+
function d3_geo_antemeridianIntersect(λ0, φ0, λ1, φ1) {
1942+
var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1);
1943+
return Math.abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2;
1944+
}
1945+
function d3_geo_antemeridianContext(p, context) {
1946+
return {
1947+
moveTo: function(x, y) {
1948+
var point = p(x, y);
1949+
context.moveTo(point[0], point[1]);
1950+
},
1951+
lineTo: function(x, y) {
1952+
var point = p(x, y);
1953+
context.lineTo(point[0], point[1]);
1954+
},
1955+
closePath: function() {
1956+
context.closePath();
1957+
}
1958+
};
1959+
}
19151960
function d3_geo_bounds(o, f) {
19161961
if (d3_geo_boundsTypes.hasOwnProperty(o.type)) d3_geo_boundsTypes[o.type](o, f);
19171962
}
@@ -2031,26 +2076,26 @@
20312076
return [ coordinates[0] * d3_degrees, coordinates[1] * d3_degrees ];
20322077
}
20332078
function reset() {
2034-
projectRotate = d3_geo_compose(d3_geo_rotation(δλ, δφ, δγ), project);
2079+
projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project);
20352080
var center = project(λ, φ);
20362081
δx = x - center[0] * k;
20372082
δy = y + center[1] * k;
20382083
return p;
20392084
}
2040-
var project, projectRotate, k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx = x, δy = y;
2085+
function rotateLocation(coordinates) {
2086+
return rotate(coordinates[0] * π / 180, coordinates[1] * π / 180);
2087+
}
2088+
function transformPoint(λ, φ) {
2089+
var point = project(λ, φ);
2090+
return [ point[0] * k + δx, δy - point[1] * k ];
2091+
}
2092+
var project, rotate, projectRotate, k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx = x, δy = y;
20412093
p.point = function(coordinates, context) {
20422094
var point = p(coordinates);
20432095
context.point(point[0], point[1]);
20442096
};
2045-
p.line = function(coordinates, context) {
2046-
var point = p(coordinates[0]), i = 0, n = coordinates.length;
2047-
context.moveTo(point[0], point[1]);
2048-
while (++i < n) context.lineTo((point = p(coordinates[i]))[0], point[1]);
2049-
};
2050-
p.ring = function(coordinates, context) {
2051-
p.line(coordinates, context);
2052-
context.closePath();
2053-
};
2097+
p.line = d3_geo_antemeridianLine(rotateLocation, transformPoint);
2098+
p.ring = d3_geo_antemeridianRing(rotateLocation, transformPoint);
20542099
p.scale = function(_) {
20552100
if (!arguments.length) return k;
20562101
k = +_;
@@ -2707,7 +2752,7 @@
27072752
d3 = {
27082753
version: "2.10.2"
27092754
};
2710-
var π = Math.PI, d3_radians = π / 180, d3_degrees = 180 / π;
2755+
var π = Math.PI, ε = 1e-6, d3_radians = π / 180, d3_degrees = 180 / π;
27112756
var d3_array = d3_arraySlice;
27122757
try {
27132758
d3_array(document.documentElement.childNodes)[0].nodeType;
@@ -6218,12 +6263,12 @@
62186263
}
62196264
var x1 = 180, x0 = -x1, y1 = 90, y0 = -y1, dx = 22.5, dy = dx;
62206265
graticule.lines = function() {
6221-
var xLines = d3.range(Math.ceil(x0 / dx) * dx, y0, dx).map(function(x) {
6222-
return [ y0, y1 ].map(function(y) {
6266+
var xSteps = d3.range(Math.ceil(x0 / dx) * dx, x1, dx).concat(x1), ySteps = d3.range(Math.ceil(y0 / dy) * dy, y1, dy).concat(y1), xLines = xSteps.map(function(x) {
6267+
return ySteps.map(function(y) {
62236268
return [ x, y ];
62246269
});
6225-
}), yLines = d3.range(Math.ceil(x1 / dy) * dy, y1, dy).map(function(y) {
6226-
return [ x0, x1 ].map(function(x) {
6270+
}), yLines = ySteps.map(function(y) {
6271+
return xSteps.map(function(x) {
62276272
return [ x, y ];
62286273
});
62296274
});

d3.v2.min.js

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

src/core/core.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
d3 = {version: "2.10.2"}; // semver
22

33
var π = Math.PI,
4+
ε = 1e-6,
45
d3_radians = π / 180,
56
d3_degrees = 180 / π;

src/geo/antemeridian.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
function d3_geo_antemeridianLine(rotate, p) {
2+
return function(lineString, context) {
3+
d3_geo_antemeridianClipLine(rotate, lineString, d3_geo_antemeridianContext(p, context));
4+
};
5+
}
6+
7+
function d3_geo_antemeridianRing(rotate, p) {
8+
return function(ring, context) {
9+
d3_geo_antemeridianClipLine(rotate, ring, d3_geo_antemeridianContext(p, context));
10+
context.closePath();
11+
};
12+
}
13+
14+
function d3_geo_antemeridianClipLine(rotate, lineString, context) {
15+
if (!(n = lineString.length)) return;
16+
var point = rotate(lineString[0]);
17+
context.moveTo(λ0 = point[0], φ0 = point[1]);
18+
for (var λ0, φ0, i = 0, j = 0, n; j < n; j++) {
19+
point = rotate(lineString[j]);
20+
var λ1 = point[0],
21+
φ1 = point[1],
22+
δλ = (Math.abs(λ1 - λ0) + 2 * π) % (2 * π),
23+
sλ0 = λ0 > 0;
24+
if (j > i && sλ0 ^ (λ1 > 0) && (δλ >= π || δλ < ε && Math.abs(Math.abs(λ0) - π) < ε)) {
25+
φ0 = d3_geo_antemeridianIntersect(λ0, φ0, λ1, φ1);
26+
context.lineTo(sλ0 ? π : -π, φ0);
27+
context.moveTo(sλ0 ? -π : π, φ0);
28+
}
29+
context.lineTo(λ0 = λ1, φ0 = φ1);
30+
}
31+
}
32+
33+
function d3_geo_antemeridianIntersect(λ0, φ0, λ1, φ1) {
34+
var cosφ0, cosφ1,
35+
sinλ0_λ1 = Math.sin(λ0 - λ1);
36+
return Math.abs(sinλ0_λ1) > ε
37+
? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1)
38+
- Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0))
39+
/ (cosφ0 * cosφ1 * sinλ0_λ1))
40+
: (φ0 + φ1) / 2;
41+
}
42+
43+
function d3_geo_antemeridianContext(p, context) {
44+
return {
45+
moveTo: function(x, y) { var point = p(x, y); context.moveTo(point[0], point[1]); },
46+
lineTo: function(x, y) { var point = p(x, y); context.lineTo(point[0], point[1]); },
47+
closePath: function() { context.closePath(); }
48+
};
49+
}

src/geo/graticule.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ d3.geo.graticule = function() {
1212
}
1313

1414
graticule.lines = function() {
15-
var xLines = d3.range(Math.ceil(x0 / dx) * dx, y0, dx).map(function(x) { return [y0, y1].map(function(y) { return [x, y]; }); }),
16-
yLines = d3.range(Math.ceil(x1 / dy) * dy, y1, dy).map(function(y) { return [x0, x1].map(function(x) { return [x, y]; }); });
15+
var xSteps = d3.range(Math.ceil(x0 / dx) * dx, x1, dx).concat(x1),
16+
ySteps = d3.range(Math.ceil(y0 / dy) * dy, y1, dy).concat(y1),
17+
xLines = xSteps.map(function(x) { return ySteps.map(function(y) { return [x, y]; }); }),
18+
yLines = ySteps.map(function(y) { return xSteps.map(function(x) { return [x, y]; }); });
1719
return xLines.concat(yLines).map(function(coordinates) {
1820
return {
1921
type: "LineString",

src/geo/projection.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ function d3_geo_projection(project) {
77
// TODO Expose this API? Not that happy with it.
88
function d3_geo_projectionMutator(projectAt) {
99
var project,
10+
rotate,
1011
projectRotate,
1112
k = 150,
1213
x = 480,
@@ -34,16 +35,8 @@ function d3_geo_projectionMutator(projectAt) {
3435
context.point(point[0], point[1]);
3536
};
3637

37-
p.line = function(coordinates, context) {
38-
var point = p(coordinates[0]), i = 0, n = coordinates.length;
39-
context.moveTo(point[0], point[1]);
40-
while (++i < n) context.lineTo((point = p(coordinates[i]))[0], point[1]);
41-
};
42-
43-
p.ring = function(coordinates, context) {
44-
p.line(coordinates, context);
45-
context.closePath();
46-
};
38+
p.line = d3_geo_antemeridianLine(rotateLocation, transformPoint);
39+
p.ring = d3_geo_antemeridianRing(rotateLocation, transformPoint);
4740

4841
p.scale = function(_) {
4942
if (!arguments.length) return k;
@@ -74,13 +67,22 @@ function d3_geo_projectionMutator(projectAt) {
7467
};
7568

7669
function reset() {
77-
projectRotate = d3_geo_compose(d3_geo_rotation(δλ, δφ, δγ), project);
70+
projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project);
7871
var center = project(λ, φ);
7972
δx = x - center[0] * k;
8073
δy = y + center[1] * k;
8174
return p;
8275
}
8376

77+
function rotateLocation(coordinates) {
78+
return rotate(coordinates[0] * π / 180, coordinates[1] * π / 180);
79+
}
80+
81+
function transformPoint(λ, φ) {
82+
var point = project(λ, φ);
83+
return [point[0] * k + δx, δy - point[1] * k];
84+
}
85+
8486
return function() {
8587
project = projectAt.apply(this, arguments);
8688
p.invert = project.invert && i;

0 commit comments

Comments
 (0)