Add d3.geo.greatCircle.polyline and precision.
This can be used to process output from d3.geo.clip to ensure clipped polygons are correctly curved. The "n" option has been replaced with precision, which denotes the approximate angular length of great-circle segments. This is much faster than using a fixed number of segments, particularly when processing a large number of polygons, only a few of which may have edges long enough to warrant being converted into a geodesic.
This commit is contained in:
Родитель
0a1af36dcc
Коммит
e284f87b0d
135
d3.geo.js
135
d3.geo.js
|
@ -624,59 +624,25 @@ function d3_geo_boundsPolygon(o, f) {
|
|||
d3.geo.greatCircle = function() {
|
||||
var source = d3_geo_greatCircleSource,
|
||||
target = d3_geo_greatCircleTarget,
|
||||
n = 100,
|
||||
coordinates = Object,
|
||||
precision = 1,
|
||||
radius = d3_geo_earthRadius;
|
||||
// TODO: breakAtDateLine?
|
||||
|
||||
function greatCircle(d, i) {
|
||||
var from = source.call(this, d, i),
|
||||
to = target.call(this, d, i),
|
||||
x0 = from[0] * d3_radians,
|
||||
y0 = from[1] * d3_radians,
|
||||
x1 = to[0] * d3_radians,
|
||||
y1 = to[1] * d3_radians,
|
||||
cx0 = Math.cos(x0), sx0 = Math.sin(x0),
|
||||
cy0 = Math.cos(y0), sy0 = Math.sin(y0),
|
||||
cx1 = Math.cos(x1), sx1 = Math.sin(x1),
|
||||
cy1 = Math.cos(y1), sy1 = Math.sin(y1),
|
||||
d = Math.acos(sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0)),
|
||||
sd = Math.sin(d),
|
||||
f = d / (n - 1),
|
||||
e = -f,
|
||||
path = [],
|
||||
i = -1;
|
||||
|
||||
while (++i < n) {
|
||||
e += f;
|
||||
var A = Math.sin(d - e) / sd,
|
||||
B = Math.sin(e) / sd,
|
||||
x = A * cy0 * cx0 + B * cy1 * cx1,
|
||||
y = A * cy0 * sx0 + B * cy1 * sx1,
|
||||
z = A * sy0 + B * sy1;
|
||||
path[i] = [
|
||||
Math.atan2(y, x) / d3_radians,
|
||||
Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_radians
|
||||
];
|
||||
}
|
||||
|
||||
return path;
|
||||
return d3_geo_greatCirclePath([
|
||||
source.call(this, d, i), target.call(this, d, i)], precision);
|
||||
}
|
||||
|
||||
greatCircle.source = function(x) {
|
||||
if (!arguments.length) return source;
|
||||
source = x;
|
||||
greatCircle.coordinates = function(x) {
|
||||
if (!arguments.length) return coordinates;
|
||||
coordinates = x;
|
||||
return greatCircle;
|
||||
};
|
||||
|
||||
greatCircle.target = function(x) {
|
||||
if (!arguments.length) return target;
|
||||
target = x;
|
||||
return greatCircle;
|
||||
};
|
||||
|
||||
greatCircle.n = function(x) {
|
||||
if (!arguments.length) return n;
|
||||
n = +x;
|
||||
greatCircle.precision = function(x) {
|
||||
if (!arguments.length) return precision;
|
||||
precision = +x;
|
||||
return greatCircle;
|
||||
};
|
||||
|
||||
|
@ -701,9 +667,62 @@ d3.geo.greatCircle = function() {
|
|||
return radius * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
};
|
||||
|
||||
greatCircle.polyline = function(d, i) {
|
||||
return d3_geo_greatCirclePath(coordinates.call(this, d, i), precision);
|
||||
};
|
||||
|
||||
return greatCircle;
|
||||
};
|
||||
|
||||
function d3_geo_greatCirclePath(coordinates, precision) {
|
||||
var m = coordinates.length;
|
||||
if (m < 2) return coordinates;
|
||||
|
||||
var i = 0,
|
||||
p = precision * d3_radians,
|
||||
from = coordinates[0],
|
||||
to,
|
||||
x0 = from[0] * d3_radians,
|
||||
y0 = from[1] * d3_radians,
|
||||
cx0 = Math.cos(x0), sx0 = Math.sin(x0),
|
||||
cy0 = Math.cos(y0), sy0 = Math.sin(y0),
|
||||
path = [from];
|
||||
|
||||
while (++i < m) {
|
||||
to = coordinates[i];
|
||||
var x1 = to[0] * d3_radians,
|
||||
y1 = to[1] * d3_radians,
|
||||
cx1 = Math.cos(x1), sx1 = Math.sin(x1),
|
||||
cy1 = Math.cos(y1), sy1 = Math.sin(y1),
|
||||
d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0)))),
|
||||
sd = Math.sin(d),
|
||||
n = Math.ceil(d / p),
|
||||
f = d / n,
|
||||
e = 0,
|
||||
j = 0;
|
||||
|
||||
while (++j < n) {
|
||||
e += f;
|
||||
var A = Math.sin(d - e) / sd,
|
||||
B = Math.sin(e) / sd,
|
||||
x = A * cy0 * cx0 + B * cy1 * cx1,
|
||||
y = A * cy0 * sx0 + B * cy1 * sx1,
|
||||
z = A * sy0 + B * sy1;
|
||||
path.push([
|
||||
Math.atan2(y, x) / d3_radians,
|
||||
Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_radians
|
||||
]);
|
||||
}
|
||||
path.push(to);
|
||||
x0 = x1;
|
||||
y0 = y1;
|
||||
cx0 = cx1; sx0 = sx1;
|
||||
cy0 = cy1; sy0 = sy1;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
function d3_geo_greatCircleSource(d) {
|
||||
return d.source;
|
||||
}
|
||||
|
@ -714,10 +733,12 @@ function d3_geo_greatCircleTarget(d) {
|
|||
d3.geo.clip = function() {
|
||||
var origin = [0, 0],
|
||||
angle = 90,
|
||||
r = d3_geo_earthRadius * angle / 180 * Math.PI;
|
||||
r = d3_geo_earthRadius * angle / 180 * Math.PI,
|
||||
coordinates = Object;
|
||||
|
||||
function clip(d) {
|
||||
var o = {source: origin, target: null},
|
||||
function clip(d, i) {
|
||||
var d = coordinates.call(this, d, i),
|
||||
o = {source: origin, target: null},
|
||||
n = d.length,
|
||||
i = -1,
|
||||
j,
|
||||
|
@ -732,10 +753,7 @@ d3.geo.clip = function() {
|
|||
if (q) {
|
||||
path = d3_geo_clipGreatCircle({source: q, target: o.target});
|
||||
j = d3_geo_clipClosest(path, o, r);
|
||||
if (p) {
|
||||
clipped.push.apply(clipped, d3_geo_clipGreatCircle({source: p, target: o.target}));
|
||||
}
|
||||
clipped.push.apply(clipped, path.slice(j));
|
||||
if (path.length) clipped.push(path[j]);
|
||||
p = q = null;
|
||||
} else {
|
||||
clipped.push(o.target);
|
||||
|
@ -745,7 +763,7 @@ d3.geo.clip = function() {
|
|||
if (!p && clipped.length) {
|
||||
path = d3_geo_clipGreatCircle({source: clipped[clipped.length - 1], target: o.target});
|
||||
j = d3_geo_clipClosest(path, o, r);
|
||||
clipped.push.apply(clipped, path.slice(0, j));
|
||||
if (path.length) clipped.push(path[j]);
|
||||
p = o.target;
|
||||
}
|
||||
}
|
||||
|
@ -754,14 +772,17 @@ d3.geo.clip = function() {
|
|||
o.target = clipped[0];
|
||||
path = d3_geo_clipGreatCircle({source: q, target: o.target});
|
||||
j = d3_geo_clipClosest(path, o, r);
|
||||
if (p) {
|
||||
clipped.push.apply(clipped, d3_geo_clipGreatCircle({source: p, target: o.target}));
|
||||
}
|
||||
clipped.push.apply(clipped, path.slice(j));
|
||||
if (path.length) clipped.push(path[j]);
|
||||
}
|
||||
return clipped;
|
||||
}
|
||||
|
||||
clip.coordinates = function(x) {
|
||||
if (!arguments.length) return coordinates;
|
||||
coordinates = x;
|
||||
return clip;
|
||||
};
|
||||
|
||||
clip.origin = function(x) {
|
||||
if (!arguments.length) return origin;
|
||||
origin = x;
|
||||
|
@ -778,7 +799,7 @@ d3.geo.clip = function() {
|
|||
return clip;
|
||||
}
|
||||
|
||||
var d3_geo_clipGreatCircle = d3.geo.greatCircle().n(100);
|
||||
var d3_geo_clipGreatCircle = d3.geo.greatCircle();
|
||||
|
||||
function d3_geo_clipClosest(path, o, r) {
|
||||
var i = -1,
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -89,7 +89,9 @@ $("#translate-y").slider({
|
|||
});
|
||||
|
||||
$("#mode").change(function() {
|
||||
xy.mode($(this).val());
|
||||
var mode = $(this).val();
|
||||
xy.mode(mode);
|
||||
path.clip(mode === "gnomonic" ? clip : circle.polyline);
|
||||
refresh(500);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
var xy = d3.geo.azimuthal().scale(240).mode("stereographic"),
|
||||
clip = d3.geo.clip().angle(89),
|
||||
path = d3.geo.path().projection(xy).clip(clip),
|
||||
circle = d3.geo.greatCircle().precision(10).coordinates(clip),
|
||||
path = d3.geo.path().projection(xy).clip(circle.polyline),
|
||||
svg = d3.select("body").append("svg:svg");
|
||||
|
||||
d3.json("../data/world-countries.json", function(collection) {
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
d3.geo.clip = function() {
|
||||
var origin = [0, 0],
|
||||
angle = 90,
|
||||
r = d3_geo_earthRadius * angle / 180 * Math.PI;
|
||||
r = d3_geo_earthRadius * angle / 180 * Math.PI,
|
||||
coordinates = Object;
|
||||
|
||||
function clip(d) {
|
||||
var o = {source: origin, target: null},
|
||||
function clip(d, i) {
|
||||
var d = coordinates.call(this, d, i),
|
||||
o = {source: origin, target: null},
|
||||
n = d.length,
|
||||
i = -1,
|
||||
j,
|
||||
|
@ -19,10 +21,7 @@ d3.geo.clip = function() {
|
|||
if (q) {
|
||||
path = d3_geo_clipGreatCircle({source: q, target: o.target});
|
||||
j = d3_geo_clipClosest(path, o, r);
|
||||
if (p) {
|
||||
clipped.push.apply(clipped, d3_geo_clipGreatCircle({source: p, target: o.target}));
|
||||
}
|
||||
clipped.push.apply(clipped, path.slice(j));
|
||||
if (path.length) clipped.push(path[j]);
|
||||
p = q = null;
|
||||
} else {
|
||||
clipped.push(o.target);
|
||||
|
@ -32,7 +31,7 @@ d3.geo.clip = function() {
|
|||
if (!p && clipped.length) {
|
||||
path = d3_geo_clipGreatCircle({source: clipped[clipped.length - 1], target: o.target});
|
||||
j = d3_geo_clipClosest(path, o, r);
|
||||
clipped.push.apply(clipped, path.slice(0, j));
|
||||
if (path.length) clipped.push(path[j]);
|
||||
p = o.target;
|
||||
}
|
||||
}
|
||||
|
@ -41,14 +40,17 @@ d3.geo.clip = function() {
|
|||
o.target = clipped[0];
|
||||
path = d3_geo_clipGreatCircle({source: q, target: o.target});
|
||||
j = d3_geo_clipClosest(path, o, r);
|
||||
if (p) {
|
||||
clipped.push.apply(clipped, d3_geo_clipGreatCircle({source: p, target: o.target}));
|
||||
}
|
||||
clipped.push.apply(clipped, path.slice(j));
|
||||
if (path.length) clipped.push(path[j]);
|
||||
}
|
||||
return clipped;
|
||||
}
|
||||
|
||||
clip.coordinates = function(x) {
|
||||
if (!arguments.length) return coordinates;
|
||||
coordinates = x;
|
||||
return clip;
|
||||
};
|
||||
|
||||
clip.origin = function(x) {
|
||||
if (!arguments.length) return origin;
|
||||
origin = x;
|
||||
|
@ -65,7 +67,7 @@ d3.geo.clip = function() {
|
|||
return clip;
|
||||
}
|
||||
|
||||
var d3_geo_clipGreatCircle = d3.geo.greatCircle().n(100);
|
||||
var d3_geo_clipGreatCircle = d3.geo.greatCircle();
|
||||
|
||||
function d3_geo_clipClosest(path, o, r) {
|
||||
var i = -1,
|
||||
|
|
|
@ -2,59 +2,25 @@
|
|||
d3.geo.greatCircle = function() {
|
||||
var source = d3_geo_greatCircleSource,
|
||||
target = d3_geo_greatCircleTarget,
|
||||
n = 100,
|
||||
coordinates = Object,
|
||||
precision = 1,
|
||||
radius = d3_geo_earthRadius;
|
||||
// TODO: breakAtDateLine?
|
||||
|
||||
function greatCircle(d, i) {
|
||||
var from = source.call(this, d, i),
|
||||
to = target.call(this, d, i),
|
||||
x0 = from[0] * d3_radians,
|
||||
y0 = from[1] * d3_radians,
|
||||
x1 = to[0] * d3_radians,
|
||||
y1 = to[1] * d3_radians,
|
||||
cx0 = Math.cos(x0), sx0 = Math.sin(x0),
|
||||
cy0 = Math.cos(y0), sy0 = Math.sin(y0),
|
||||
cx1 = Math.cos(x1), sx1 = Math.sin(x1),
|
||||
cy1 = Math.cos(y1), sy1 = Math.sin(y1),
|
||||
d = Math.acos(sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0)),
|
||||
sd = Math.sin(d),
|
||||
f = d / (n - 1),
|
||||
e = -f,
|
||||
path = [],
|
||||
i = -1;
|
||||
|
||||
while (++i < n) {
|
||||
e += f;
|
||||
var A = Math.sin(d - e) / sd,
|
||||
B = Math.sin(e) / sd,
|
||||
x = A * cy0 * cx0 + B * cy1 * cx1,
|
||||
y = A * cy0 * sx0 + B * cy1 * sx1,
|
||||
z = A * sy0 + B * sy1;
|
||||
path[i] = [
|
||||
Math.atan2(y, x) / d3_radians,
|
||||
Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_radians
|
||||
];
|
||||
}
|
||||
|
||||
return path;
|
||||
return d3_geo_greatCirclePath([
|
||||
source.call(this, d, i), target.call(this, d, i)], precision);
|
||||
}
|
||||
|
||||
greatCircle.source = function(x) {
|
||||
if (!arguments.length) return source;
|
||||
source = x;
|
||||
greatCircle.coordinates = function(x) {
|
||||
if (!arguments.length) return coordinates;
|
||||
coordinates = x;
|
||||
return greatCircle;
|
||||
};
|
||||
|
||||
greatCircle.target = function(x) {
|
||||
if (!arguments.length) return target;
|
||||
target = x;
|
||||
return greatCircle;
|
||||
};
|
||||
|
||||
greatCircle.n = function(x) {
|
||||
if (!arguments.length) return n;
|
||||
n = +x;
|
||||
greatCircle.precision = function(x) {
|
||||
if (!arguments.length) return precision;
|
||||
precision = +x;
|
||||
return greatCircle;
|
||||
};
|
||||
|
||||
|
@ -79,9 +45,62 @@ d3.geo.greatCircle = function() {
|
|||
return radius * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
};
|
||||
|
||||
greatCircle.polyline = function(d, i) {
|
||||
return d3_geo_greatCirclePath(coordinates.call(this, d, i), precision);
|
||||
};
|
||||
|
||||
return greatCircle;
|
||||
};
|
||||
|
||||
function d3_geo_greatCirclePath(coordinates, precision) {
|
||||
var m = coordinates.length;
|
||||
if (m < 2) return coordinates;
|
||||
|
||||
var i = 0,
|
||||
p = precision * d3_radians,
|
||||
from = coordinates[0],
|
||||
to,
|
||||
x0 = from[0] * d3_radians,
|
||||
y0 = from[1] * d3_radians,
|
||||
cx0 = Math.cos(x0), sx0 = Math.sin(x0),
|
||||
cy0 = Math.cos(y0), sy0 = Math.sin(y0),
|
||||
path = [from];
|
||||
|
||||
while (++i < m) {
|
||||
to = coordinates[i];
|
||||
var x1 = to[0] * d3_radians,
|
||||
y1 = to[1] * d3_radians,
|
||||
cx1 = Math.cos(x1), sx1 = Math.sin(x1),
|
||||
cy1 = Math.cos(y1), sy1 = Math.sin(y1),
|
||||
d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0)))),
|
||||
sd = Math.sin(d),
|
||||
n = Math.ceil(d / p),
|
||||
f = d / n,
|
||||
e = 0,
|
||||
j = 0;
|
||||
|
||||
while (++j < n) {
|
||||
e += f;
|
||||
var A = Math.sin(d - e) / sd,
|
||||
B = Math.sin(e) / sd,
|
||||
x = A * cy0 * cx0 + B * cy1 * cx1,
|
||||
y = A * cy0 * sx0 + B * cy1 * sx1,
|
||||
z = A * sy0 + B * sy1;
|
||||
path.push([
|
||||
Math.atan2(y, x) / d3_radians,
|
||||
Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_radians
|
||||
]);
|
||||
}
|
||||
path.push(to);
|
||||
x0 = x1;
|
||||
y0 = y1;
|
||||
cx0 = cx1; sx0 = sx1;
|
||||
cy0 = cy1; sy0 = sy1;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
function d3_geo_greatCircleSource(d) {
|
||||
return d.source;
|
||||
}
|
||||
|
|
|
@ -10,8 +10,7 @@ var suite = vows.describe("d3.geo.greatCircle");
|
|||
suite.addBatch({
|
||||
"greatCircle": {
|
||||
topic: function() {
|
||||
return d3.geo.greatCircle()
|
||||
.n(12);
|
||||
return d3.geo.greatCircle().precision(7.5);
|
||||
},
|
||||
"distance": function(circle) {
|
||||
assert.equal(circle.distance({source: [0, 0], target: [0, 0]}), 0);
|
||||
|
|
Загрузка…
Ссылка в новой задаче