Redesigned d3.geo.albersUsa.
This commit changes the albersUsa projection so that it uses a custom projection stream rather than a custom projection function. The projection stream passes geometry to all three constituent projections, which are then clipped to their respective viewports. The result is that geometry that appears in multiple projections (such as a graticule) is now rendered correctly in each, with clipping! In addition, the inverse projection is much easier to compute because the viewport regions of each projection are explicitly defined.
This commit is contained in:
Родитель
d1d71e1689
Коммит
e8fcad0cfb
|
@ -3360,53 +3360,72 @@ d3 = function() {
|
|||
return d3_geo_conic(d3_geo_conicEqualArea);
|
||||
}).raw = d3_geo_conicEqualArea;
|
||||
d3.geo.albersUsa = function() {
|
||||
var lower48 = d3.geo.conicEqualArea().rotate([ 98, 0 ]).center([ 0, 38 ]).parallels([ 29.5, 45.5 ]);
|
||||
var alaska = d3.geo.conicEqualArea().rotate([ 160, 0 ]).center([ 0, 60 ]).parallels([ 55, 65 ]);
|
||||
var lower48 = d3.geo.conicEqualArea().rotate([ 96, 0 ]).center([ -.6, 38.7 ]).parallels([ 29.5, 45.5 ]);
|
||||
var alaska = d3.geo.conicEqualArea().rotate([ 160, 0, -35 ]).center([ 45, 44 ]).parallels([ 55, 65 ]);
|
||||
var hawaii = d3.geo.conicEqualArea().rotate([ 160, 0 ]).center([ 0, 20 ]).parallels([ 8, 18 ]);
|
||||
var puertoRico = d3.geo.conicEqualArea().rotate([ 60, 0 ]).center([ 0, 10 ]).parallels([ 8, 18 ]);
|
||||
var alaskaInvert, hawaiiInvert, puertoRicoInvert;
|
||||
function albersUsa(coordinates) {
|
||||
return projection(coordinates)(coordinates);
|
||||
}
|
||||
function projection(point) {
|
||||
var lon = point[0], lat = point[1];
|
||||
return lat > 50 ? alaska : lon < -140 ? hawaii : lat < 21 ? puertoRico : lower48;
|
||||
var y = point[0], x = point[1];
|
||||
return (x > 50 ? alaska : y < -140 ? hawaii : lower48)(coordinates);
|
||||
}
|
||||
albersUsa.invert = function(coordinates) {
|
||||
return alaskaInvert(coordinates) || hawaiiInvert(coordinates) || puertoRicoInvert(coordinates) || lower48.invert(coordinates);
|
||||
var k = lower48.scale(), t = lower48.translate(), x = (coordinates[0] - t[0]) / k, y = (coordinates[1] - t[1]) / k;
|
||||
return (y >= .123 && y < .23 && x >= -.427 && x < -.215 ? alaska : y >= .16 && y < .23 && x >= -.215 && x < -.115 ? hawaii : lower48).invert(coordinates);
|
||||
};
|
||||
albersUsa.scale = function(x) {
|
||||
albersUsa.stream = d3_geo_albersUsaStream([ lower48, alaska, hawaii ]);
|
||||
albersUsa.scale = function(_) {
|
||||
if (!arguments.length) return lower48.scale();
|
||||
lower48.scale(x);
|
||||
alaska.scale(x * .6);
|
||||
hawaii.scale(x);
|
||||
puertoRico.scale(x * 1.5);
|
||||
lower48.scale(_);
|
||||
alaska.scale(_ * .35);
|
||||
hawaii.scale(_);
|
||||
return albersUsa.translate(lower48.translate());
|
||||
};
|
||||
albersUsa.translate = function(x) {
|
||||
albersUsa.translate = function(_) {
|
||||
if (!arguments.length) return lower48.translate();
|
||||
var dz = lower48.scale(), dx = x[0], dy = x[1];
|
||||
lower48.translate(x);
|
||||
alaska.translate([ dx - .4 * dz, dy + .17 * dz ]);
|
||||
hawaii.translate([ dx - .19 * dz, dy + .2 * dz ]);
|
||||
puertoRico.translate([ dx + .58 * dz, dy + .43 * dz ]);
|
||||
alaskaInvert = d3_geo_albersUsaInvert(alaska, [ [ -180, 50 ], [ -130, 72 ] ]);
|
||||
hawaiiInvert = d3_geo_albersUsaInvert(hawaii, [ [ -164, 18 ], [ -154, 24 ] ]);
|
||||
puertoRicoInvert = d3_geo_albersUsaInvert(puertoRico, [ [ -67.5, 17.5 ], [ -65, 19 ] ]);
|
||||
var k = lower48.scale(), x = +_[0], y = +_[1];
|
||||
lower48.translate(_).clipExtent([ [ x - .455 * k, y - .238 * k ], [ x + .455 * k, y + .238 * k ] ]);
|
||||
alaska.translate([ x - .307 * k, y + .197 * k ]).clipExtent([ [ x - .427 * k, y + .123 * k ], [ x - .215 * k, y + .23 * k ] ]);
|
||||
hawaii.translate([ x - .205 * k, y + .205 * k ]).clipExtent([ [ x - .215 * k, y + .16 * k ], [ x - .115 * k, y + .23 * k ] ]);
|
||||
return albersUsa;
|
||||
};
|
||||
return albersUsa.scale(1e3);
|
||||
return albersUsa.scale(1056);
|
||||
};
|
||||
function d3_geo_albersUsaInvert(projection, extent) {
|
||||
var a = projection(extent[0]), b = projection([ .5 * (extent[0][0] + extent[1][0]), extent[0][1] ]), c = projection([ extent[1][0], extent[0][1] ]), d = projection(extent[1]);
|
||||
var dya = b[1] - a[1], dxa = b[0] - a[0], dyb = c[1] - b[1], dxb = c[0] - b[0];
|
||||
var ma = dya / dxa, mb = dyb / dxb;
|
||||
var cx = .5 * (ma * mb * (a[1] - c[1]) + mb * (a[0] + b[0]) - ma * (b[0] + c[0])) / (mb - ma), cy = (.5 * (a[0] + b[0]) - cx) / ma + .5 * (a[1] + b[1]);
|
||||
var dx0 = d[0] - cx, dy0 = d[1] - cy, dx1 = a[0] - cx, dy1 = a[1] - cy, r0 = dx0 * dx0 + dy0 * dy0, r1 = dx1 * dx1 + dy1 * dy1;
|
||||
var a0 = Math.atan2(dy0, dx0), a1 = Math.atan2(dy1, dx1);
|
||||
return function(coordinates) {
|
||||
var dx = coordinates[0] - cx, dy = coordinates[1] - cy, r = dx * dx + dy * dy, a = Math.atan2(dy, dx);
|
||||
if (r0 < r && r < r1 && a0 < a && a < a1) return projection.invert(coordinates);
|
||||
function d3_geo_albersUsaStream(projections) {
|
||||
return function(stream) {
|
||||
var streams = projections.map(function(p) {
|
||||
return p.stream(stream);
|
||||
});
|
||||
return {
|
||||
point: function(x, y) {
|
||||
streams.forEach(function(s) {
|
||||
s.point(x, y);
|
||||
});
|
||||
},
|
||||
sphere: function(x, y) {
|
||||
streams.forEach(function(s) {
|
||||
s.sphere();
|
||||
});
|
||||
},
|
||||
lineStart: function(x, y) {
|
||||
streams.forEach(function(s) {
|
||||
s.lineStart();
|
||||
});
|
||||
},
|
||||
lineEnd: function(x, y) {
|
||||
streams.forEach(function(s) {
|
||||
s.lineEnd();
|
||||
});
|
||||
},
|
||||
polygonStart: function(x, y) {
|
||||
streams.forEach(function(s) {
|
||||
s.polygonStart();
|
||||
});
|
||||
},
|
||||
polygonEnd: function(x, y) {
|
||||
streams.forEach(function(s) {
|
||||
s.polygonEnd();
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = {
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -6,13 +6,13 @@ import "geo";
|
|||
// http://egsc.usgs.gov/isb/pubs/MapProjections/projections.html#albers
|
||||
d3.geo.albersUsa = function() {
|
||||
var lower48 = d3.geo.conicEqualArea()
|
||||
.rotate([98, 0])
|
||||
.center([0, 38])
|
||||
.rotate([96, 0])
|
||||
.center([-.6, 38.7])
|
||||
.parallels([29.5, 45.5]);
|
||||
|
||||
var alaska = d3.geo.conicEqualArea()
|
||||
.rotate([160, 0])
|
||||
.center([0, 60])
|
||||
.rotate([160, 0, -35])
|
||||
.center([45, 44])
|
||||
.parallels([55, 65]);
|
||||
|
||||
var hawaii = d3.geo.conicEqualArea()
|
||||
|
@ -20,96 +20,65 @@ d3.geo.albersUsa = function() {
|
|||
.center([0, 20])
|
||||
.parallels([8, 18]);
|
||||
|
||||
var puertoRico = d3.geo.conicEqualArea()
|
||||
.rotate([60, 0])
|
||||
.center([0, 10])
|
||||
.parallels([8, 18]);
|
||||
|
||||
var alaskaInvert,
|
||||
hawaiiInvert,
|
||||
puertoRicoInvert;
|
||||
|
||||
function albersUsa(coordinates) {
|
||||
return projection(coordinates)(coordinates);
|
||||
}
|
||||
|
||||
function projection(point) {
|
||||
var lon = point[0],
|
||||
lat = point[1];
|
||||
return lat > 50 ? alaska
|
||||
: lon < -140 ? hawaii
|
||||
: lat < 21 ? puertoRico
|
||||
: lower48;
|
||||
var y = coordinates[0], x = coordinates[1];
|
||||
return (x > 50 ? alaska : y < -140 ? hawaii : lower48)(coordinates);
|
||||
}
|
||||
|
||||
albersUsa.invert = function(coordinates) {
|
||||
return alaskaInvert(coordinates) || hawaiiInvert(coordinates) || puertoRicoInvert(coordinates) || lower48.invert(coordinates);
|
||||
var k = lower48.scale(),
|
||||
t = lower48.translate(),
|
||||
x = (coordinates[0] - t[0]) / k,
|
||||
y = (coordinates[1] - t[1]) / k;
|
||||
return (y >= .123 && y < .230 && x >= -.427 && x < -.215 ? alaska
|
||||
: y >= .160 && y < .230 && x >= -.215 && x < -.115 ? hawaii
|
||||
: lower48).invert(coordinates);
|
||||
};
|
||||
|
||||
albersUsa.scale = function(x) {
|
||||
albersUsa.stream = d3_geo_albersUsaStream([lower48, alaska, hawaii]);
|
||||
|
||||
albersUsa.scale = function(_) {
|
||||
if (!arguments.length) return lower48.scale();
|
||||
lower48.scale(x);
|
||||
alaska.scale(x * .6);
|
||||
hawaii.scale(x);
|
||||
puertoRico.scale(x * 1.5);
|
||||
lower48.scale(_);
|
||||
alaska.scale(_ * .35);
|
||||
hawaii.scale(_);
|
||||
return albersUsa.translate(lower48.translate());
|
||||
};
|
||||
|
||||
albersUsa.translate = function(x) {
|
||||
albersUsa.translate = function(_) {
|
||||
if (!arguments.length) return lower48.translate();
|
||||
var dz = lower48.scale(),
|
||||
dx = x[0],
|
||||
dy = x[1];
|
||||
lower48.translate(x);
|
||||
alaska.translate([dx - .40 * dz, dy + .17 * dz]);
|
||||
hawaii.translate([dx - .19 * dz, dy + .20 * dz]);
|
||||
puertoRico.translate([dx + .58 * dz, dy + .43 * dz]);
|
||||
var k = lower48.scale(), x = +_[0], y = +_[1];
|
||||
|
||||
alaskaInvert = d3_geo_albersUsaInvert(alaska, [[-180, 50], [-130, 72]]);
|
||||
hawaiiInvert = d3_geo_albersUsaInvert(hawaii, [[-164, 18], [-154, 24]]);
|
||||
puertoRicoInvert = d3_geo_albersUsaInvert(puertoRico, [[-67.5, 17.5], [-65, 19]]);
|
||||
lower48
|
||||
.translate(_)
|
||||
.clipExtent([[x - .455 * k, y - .238 * k], [x + .455 * k, y + .238 * k]]);
|
||||
|
||||
alaska
|
||||
.translate([x - .307 * k, y + .197 * k])
|
||||
.clipExtent([[x - .427 * k, y + .123 * k], [x - .215 * k, y + .230 * k]]);
|
||||
|
||||
hawaii
|
||||
.translate([x - .205 * k, y + .205 * k])
|
||||
.clipExtent([[x - .215 * k, y + .160 * k], [x - .115 * k, y + .230 * k]]);
|
||||
|
||||
return albersUsa;
|
||||
};
|
||||
|
||||
return albersUsa.scale(1000);
|
||||
return albersUsa.scale(1056);
|
||||
};
|
||||
|
||||
function d3_geo_albersUsaInvert(projection, extent) {
|
||||
var a = projection(extent[0]),
|
||||
b = projection([.5 * (extent[0][0] + extent[1][0]), extent[0][1]]),
|
||||
c = projection([extent[1][0], extent[0][1]]),
|
||||
d = projection(extent[1]);
|
||||
|
||||
var dya = b[1]- a[1],
|
||||
dxa = b[0]- a[0],
|
||||
dyb = c[1]- b[1],
|
||||
dxb = c[0]- b[0];
|
||||
|
||||
var ma = dya / dxa,
|
||||
mb = dyb / dxb;
|
||||
|
||||
// Find center of circle going through points [a, b, c].
|
||||
var cx = .5 * (ma * mb * (a[1] - c[1]) + mb * (a[0] + b[0]) - ma * (b[0] + c[0])) / (mb - ma),
|
||||
cy = (.5 * (a[0] + b[0]) - cx) / ma + .5 * (a[1] + b[1]);
|
||||
|
||||
// Radial distance² from center.
|
||||
var dx0 = d[0] - cx,
|
||||
dy0 = d[1] - cy,
|
||||
dx1 = a[0] - cx,
|
||||
dy1 = a[1] - cy,
|
||||
r0 = dx0 * dx0 + dy0 * dy0,
|
||||
r1 = dx1 * dx1 + dy1 * dy1;
|
||||
|
||||
// Angular extent.
|
||||
var a0 = Math.atan2(dy0, dx0),
|
||||
a1 = Math.atan2(dy1, dx1);
|
||||
|
||||
return function(coordinates) {
|
||||
var dx = coordinates[0] - cx,
|
||||
dy = coordinates[1] - cy,
|
||||
r = dx * dx + dy * dy,
|
||||
a = Math.atan2(dy, dx);
|
||||
if (r0 < r && r < r1 && a0 < a && a < a1) return projection.invert(coordinates);
|
||||
// A naïve multi-projection stream.
|
||||
// It probably only works because clipping buffers internally.
|
||||
function d3_geo_albersUsaStream(projections) {
|
||||
return function(stream) {
|
||||
var streams = projections.map(function(p) { return p.stream(stream); });
|
||||
return {
|
||||
point: function(x, y) { streams.forEach(function(s) { s.point(x, y); }); },
|
||||
sphere: function(x, y) { streams.forEach(function(s) { s.sphere(); }); },
|
||||
lineStart: function(x, y) { streams.forEach(function(s) { s.lineStart(); }); },
|
||||
lineEnd: function(x, y) { streams.forEach(function(s) { s.lineEnd(); }); },
|
||||
polygonStart: function(x, y) { streams.forEach(function(s) { s.polygonStart(); }); },
|
||||
polygonEnd: function(x, y) { streams.forEach(function(s) { s.polygonEnd(); }); }
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -334,13 +334,13 @@ suite.addBatch({
|
|||
},
|
||||
"coerces point radius to a number": {
|
||||
"when the radius is specified as a constant": function(path) {
|
||||
var p = path().context(testContext).pointRadius("6");
|
||||
var p = path().projection(null).context(testContext).pointRadius("6");
|
||||
assert.strictEqual(p.pointRadius(), 6);
|
||||
p({type: "Point", coordinates: [0, 0]});
|
||||
assert.strictEqual(testContext.buffer().filter(function(d) { return d.type === "arc"; })[0].r, 6);
|
||||
},
|
||||
"when the radius is specified as a function": function(path) {
|
||||
var p = path().context(testContext).pointRadius(function() { return "6"; });
|
||||
var p = path().projection(null).context(testContext).pointRadius(function() { return "6"; });
|
||||
p({type: "Point", coordinates: [0, 0]});
|
||||
assert.strictEqual(testContext.buffer().filter(function(d) { return d.type === "arc"; })[0].r, 6);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче