Detect polygon direction using area calculation.
This fixes various problems that were occurring due to winding numbers being tricky on a finite sphere. This also includes some cleanup and fixes for degenerate points.
This commit is contained in:
Родитель
acbce53ec5
Коммит
e6ab7219b8
|
@ -5497,7 +5497,7 @@
|
|||
var o = typeof origin === "function" ? origin.apply(this, arguments) : origin;
|
||||
rotate = d3_geo_rotation(-o[0] * d3_radians, -o[1] * d3_radians, 0);
|
||||
var ring = [];
|
||||
d3_geo_circleInterpolateCircle(interpolate, {
|
||||
interpolate(null, null, 1, {
|
||||
lineTo: function(λ, φ) {
|
||||
var point = rotate.invert(λ, φ);
|
||||
point[0] *= d3_degrees;
|
||||
|
@ -5522,13 +5522,13 @@
|
|||
};
|
||||
circle.precision = function(_) {
|
||||
if (!arguments.length) return precision;
|
||||
interpolate = d3_geo_circleInterpolate(radians, (precision = +_) * d3_radians);
|
||||
interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians);
|
||||
return circle;
|
||||
};
|
||||
return circle.angle(90);
|
||||
};
|
||||
function d3_geo_circleClip(degrees, rotate) {
|
||||
var radians = degrees * d3_radians, cr = Math.cos(radians), center = [ cr, 0, 0 ], angle = d3_geo_circleAngle(center), interpolate = d3_geo_circleInterpolate(radians, 6 * d3_radians);
|
||||
var radians = degrees * d3_radians, cr = Math.cos(radians), interpolate = d3_geo_circleInterpolate(radians, 6 * d3_radians);
|
||||
return {
|
||||
point: function(coordinates, context) {
|
||||
if (visible(coordinates = rotate(coordinates))) {
|
||||
|
@ -5539,46 +5539,54 @@
|
|||
clipLine(coordinates, context);
|
||||
},
|
||||
polygon: function(polygon, context) {
|
||||
d3_geo_circleClipPolygon(polygon, context, clipLine, interpolate, angle);
|
||||
d3_geo_circleClipPolygon(polygon, context, clipLine, interpolate);
|
||||
}
|
||||
};
|
||||
function visible(point) {
|
||||
return Math.cos(point[1]) * Math.cos(point[0]) > cr;
|
||||
}
|
||||
function clipLine(coordinates, context, winding) {
|
||||
if (!(n = coordinates.length)) return;
|
||||
var point0 = rotate(coordinates[0]), point2, inside = visible(point0), keepWinding = winding != null, closed = keepWinding && inside, n, move0, line0;
|
||||
if (inside) context.moveTo((move0 = point0)[0], point0[1]);
|
||||
function clipLine(coordinates, context, ring) {
|
||||
if (!(n = coordinates.length)) return [ ring && 0, false ];
|
||||
var point0 = rotate(coordinates[0]), point1, point2, v0 = visible(point0), v00 = ring && v0, v, n, clean = ring, area = 0, p, x0, x, y0, y;
|
||||
if (clean) {
|
||||
x0 = (p = d3_geo_stereographic(point0[0] + (v0 ? 0 : π), point0[1]))[0];
|
||||
y0 = p[1];
|
||||
}
|
||||
if (v0) context.moveTo(point0[0], point0[1]);
|
||||
for (var i = 1; i < n; i++) {
|
||||
var point1 = rotate(coordinates[i]), v = visible(point1);
|
||||
if (v !== inside) {
|
||||
if (inside = v) {
|
||||
point2 = intersect(point1, point0);
|
||||
if (!line0 || Math.abs(line0[0] - point2[0]) > ε || Math.abs(line0[1] - point2[1]) > ε) {
|
||||
if (move0) keepWinding = false;
|
||||
context.moveTo((move0 = point2)[0], point2[1]);
|
||||
}
|
||||
if (keepWinding) winding += d3_geo_circleWinding(point2, point1);
|
||||
point0 = point2;
|
||||
} else {
|
||||
line0 = point2 = intersect(point0, point1);
|
||||
context.lineTo(point2[0], point2[1]);
|
||||
if (keepWinding) {
|
||||
if (Math.abs(move0[0] - point2[0]) > ε || Math.abs(move0[1] - point2[1]) > ε) {
|
||||
keepWinding = false;
|
||||
} else {
|
||||
winding += d3_geo_circleWinding(point0, move0);
|
||||
}
|
||||
}
|
||||
point0 = point2;
|
||||
point1 = rotate(coordinates[i]);
|
||||
v = visible(point1);
|
||||
if (v !== v0) {
|
||||
point2 = intersect(point0, point1);
|
||||
if (pointsEqual(point0, point2) || pointsEqual(point1, point2)) {
|
||||
point1[0] += ε;
|
||||
point1[1] += ε;
|
||||
v = visible(point1);
|
||||
}
|
||||
}
|
||||
if (keepWinding) winding += d3_geo_circleWinding(point0, point1);
|
||||
if (v) context.lineTo(point1[0], point1[1]);
|
||||
if (v !== v0) {
|
||||
clean = false;
|
||||
if (v0 = v) {
|
||||
point2 = intersect(point1, point0);
|
||||
context.moveTo(point2[0], point2[1]);
|
||||
} else {
|
||||
point2 = intersect(point0, point1);
|
||||
context.lineTo(point2[0], point2[1]);
|
||||
}
|
||||
point0 = point2;
|
||||
}
|
||||
if (clean) {
|
||||
p = d3_geo_stereographic(point1[0] + (v ? 0 : π), point1[1]);
|
||||
x = p[0];
|
||||
y = p[1];
|
||||
area += y0 * x - x0 * y;
|
||||
x0 = x;
|
||||
y0 = y;
|
||||
}
|
||||
if (v && !pointsEqual(point0, point1)) context.lineTo(point1[0], point1[1]);
|
||||
point0 = point1;
|
||||
}
|
||||
if (closed && v) context.closePath();
|
||||
return keepWinding && (!move0 || Math.abs(move0[0] - point0[0]) < ε && Math.abs(move0[1] - point0[1]) < ε) && winding;
|
||||
return [ clean && area * .5, v00 && v ];
|
||||
}
|
||||
function intersect(a, b) {
|
||||
var pa = d3_geo_circleCartesian(a, [ 0, 0, 0 ]), pb = d3_geo_circleCartesian(b, [ 0, 0, 0 ]);
|
||||
|
@ -5594,44 +5602,56 @@
|
|||
function d3_geo_circleInterpolate(radians, precision) {
|
||||
var cr = Math.cos(radians), sr = Math.sin(radians);
|
||||
return function(from, to, direction, context) {
|
||||
var step = direction * precision;
|
||||
from = from.angle;
|
||||
to = to.angle;
|
||||
if (from < to) from += 2 * π;
|
||||
for (var step = precision, t = from; direction > 0 ? t > to : t < to; t -= step) {
|
||||
if (from != null) {
|
||||
from = d3_geo_circleAngle(cr, from);
|
||||
to = d3_geo_circleAngle(cr, to);
|
||||
if (direction > 0 ? from < to : from > to) from += direction * 2 * π;
|
||||
} else {
|
||||
from = radians + direction * 2 * π;
|
||||
to = radians;
|
||||
}
|
||||
for (var step = direction * precision, t = from; direction > 0 ? t > to : t < to; t -= step) {
|
||||
var c = Math.cos(t), s = Math.sin(t), point = d3_geo_circleSpherical([ cr, -sr * c, -sr * s ]);
|
||||
context.lineTo(point[0], point[1]);
|
||||
}
|
||||
};
|
||||
}
|
||||
function d3_geo_circleClipPolygon(coordinates, context, clipLine, interpolate, angle) {
|
||||
var subject = [], clip = [], segments = [], buffer = d3_geo_circleBufferSegments(clipLine), winding = 0, count = 0;
|
||||
function d3_geo_circleClipPolygon(coordinates, context, clipLine, interpolate) {
|
||||
var subject = [], clip = [], segments = [], buffer = d3_geo_circleBufferSegments(clipLine), draw = [], visibleArea = 0, invisibleArea = 0, invisible = false;
|
||||
coordinates.forEach(function(ring) {
|
||||
var x = buffer(ring, context), ringSegments = x[1];
|
||||
winding += x[0];
|
||||
var n = ringSegments.length;
|
||||
if (!n) return;
|
||||
count += n;
|
||||
if (typeof x[0] === "number") {
|
||||
var segment = ringSegments[0], point = segment[0], n = segment.length - 1, i = 0;
|
||||
var x = buffer(ring, context), ringSegments = x[1], segment, n = ringSegments.length;
|
||||
if (!n) {
|
||||
invisible = true;
|
||||
invisibleArea += x[0][0];
|
||||
return;
|
||||
}
|
||||
if (x[0][0] !== false) {
|
||||
visibleArea += x[0][0];
|
||||
draw.push(segment = ringSegments[0]);
|
||||
var point = segment[0], n = segment.length - 1, i = 0;
|
||||
context.moveTo(point[0], point[1]);
|
||||
while (++i < n) context.lineTo((point = segment[i])[0], point[1]);
|
||||
context.closePath();
|
||||
return;
|
||||
}
|
||||
segments = segments.concat(ringSegments);
|
||||
if (n > 1 && x[0][1]) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
|
||||
segments = segments.concat(ringSegments.filter(d3_geo_circleSegmentLength1));
|
||||
});
|
||||
if (count ? winding > 0 : winding < 0) {
|
||||
var moved = false;
|
||||
d3_geo_circleInterpolateCircle(interpolate, {
|
||||
lineTo: function(x, y) {
|
||||
(moved ? context.lineTo : (moved = true, context.moveTo))(x, y);
|
||||
}
|
||||
});
|
||||
context.closePath();
|
||||
if (!segments.length) {
|
||||
if (visibleArea < 0 || invisible && invisibleArea < 0) {
|
||||
var moved = false;
|
||||
interpolate(null, null, 1, {
|
||||
lineTo: function(x, y) {
|
||||
(moved ? context.lineTo : (moved = true, context.moveTo))(x, y);
|
||||
}
|
||||
});
|
||||
context.closePath();
|
||||
}
|
||||
}
|
||||
segments.forEach(function(segment) {
|
||||
var p0 = segment[0], p1 = segment[segment.length - 1], a = {
|
||||
var n = segment.length;
|
||||
if (n <= 1) return;
|
||||
var p0 = segment[0], p1 = segment[n - 1], a = {
|
||||
point: p0,
|
||||
points: segment,
|
||||
other: null,
|
||||
|
@ -5640,7 +5660,6 @@
|
|||
subject: true
|
||||
}, b = {
|
||||
point: p0,
|
||||
angle: angle(p0),
|
||||
points: [ p0 ],
|
||||
other: a,
|
||||
visited: false,
|
||||
|
@ -5660,7 +5679,6 @@
|
|||
};
|
||||
b = {
|
||||
point: p1,
|
||||
angle: angle(p1),
|
||||
points: [ p1 ],
|
||||
other: a,
|
||||
visited: false,
|
||||
|
@ -5687,7 +5705,7 @@
|
|||
if (current.subject) {
|
||||
for (var i = 0; i < points.length; i++) context.lineTo((point = points[i])[0], point[1]);
|
||||
} else {
|
||||
interpolate(current, current.next, 1, context);
|
||||
interpolate(current.point, current.next.point, 1, context);
|
||||
}
|
||||
current = current.next;
|
||||
} else {
|
||||
|
@ -5695,7 +5713,7 @@
|
|||
points = current.prev.points;
|
||||
for (var i = points.length; --i >= 0; ) context.lineTo((point = points[i])[0], point[1]);
|
||||
} else {
|
||||
interpolate(current, current.prev, -1, context);
|
||||
interpolate(current.point, current.prev.point, -1, context);
|
||||
}
|
||||
current = current.prev;
|
||||
}
|
||||
|
@ -5713,15 +5731,13 @@
|
|||
}
|
||||
}
|
||||
function d3_geo_circleClipSort(a, b) {
|
||||
return b.angle - a.angle;
|
||||
return ((a = a.point)[0] < 0 ? a[1] - π / 2 - ε : π / 2 - a[1]) - ((b = b.point)[0] < 0 ? b[1] - π / 2 - ε : π / 2 - b[1]);
|
||||
}
|
||||
function d3_geo_circleAngle(center) {
|
||||
return function(point) {
|
||||
var a = d3_geo_circleCartesian(point, center);
|
||||
d3_geo_circleNormalize(a);
|
||||
var angle = Math.acos(Math.max(-1, Math.min(1, -a[1])));
|
||||
return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI) % (2 * Math.PI);
|
||||
};
|
||||
function d3_geo_circleAngle(cr, point) {
|
||||
var a = d3_geo_circleCartesian(point, [ cr, 0, 0 ]);
|
||||
d3_geo_circleNormalize(a);
|
||||
var angle = Math.acos(Math.max(-1, Math.min(1, -a[1])));
|
||||
return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI);
|
||||
}
|
||||
function d3_geo_circleCartesian(point, origin) {
|
||||
var p0 = point[0], p1 = point[1], c1 = Math.cos(p1);
|
||||
|
@ -5754,40 +5770,20 @@
|
|||
return function(coordinates) {
|
||||
var segments = [], segment;
|
||||
return [ f(coordinates, {
|
||||
point: d3_noop,
|
||||
moveTo: function(x, y) {
|
||||
segments.push(segment = [ [ x, y ] ]);
|
||||
},
|
||||
lineTo: function(x, y) {
|
||||
segment.push([ x, y ]);
|
||||
},
|
||||
closePath: function() {
|
||||
if (segments.length < 2) return;
|
||||
segments.pop();
|
||||
segments.push(segment = segment.concat(segments.shift()));
|
||||
}
|
||||
}, 0), segments ];
|
||||
}, true), segments ];
|
||||
};
|
||||
}
|
||||
function d3_geo_circleWinding(p0, p) {
|
||||
var c0 = Math.cos(p0[1]), k0 = 1 + Math.cos(p0[0]) * c0, c = Math.cos(p[1]), k = 1 + Math.cos(p[0]) * c;
|
||||
p0 = [ c0 * Math.sin(p0[0]) / k0, Math.sin(p0[1]) / k0 ];
|
||||
p = [ c * Math.sin(p[0]) / k, Math.sin(p[1]) / k ];
|
||||
if (p0[1] <= 0) {
|
||||
if (p[1] > 0 && (p0[0] - p[0]) * p0[1] + p0[0] * (p[1] - p0[1]) > 0) return 1;
|
||||
} else {
|
||||
if (p[1] <= 0 && (p0[0] - p[0]) * p0[1] + p0[0] * (p[1] - p0[1]) < 0) return -1;
|
||||
}
|
||||
return 0;
|
||||
function pointsEqual(a, b) {
|
||||
return Math.abs(a[0] - b[0]) < ε && Math.abs(a[1] - b[1]) < ε;
|
||||
}
|
||||
function d3_geo_circleInterpolateCircle(interpolate, context) {
|
||||
for (var i = 0; i < 4; i++) {
|
||||
interpolate({
|
||||
angle: -i * π / 2
|
||||
}, {
|
||||
angle: -(i + 1) * π / 2
|
||||
}, 1, context);
|
||||
}
|
||||
function d3_geo_circleSegmentLength1(segment) {
|
||||
return segment.length > 1;
|
||||
}
|
||||
function d3_geo_compose(a, b) {
|
||||
if (a === d3_geo_equirectangular) return b;
|
||||
|
@ -6249,9 +6245,9 @@
|
|||
var point = rotatePoint(coordinates);
|
||||
context.point(point[0], point[1]);
|
||||
},
|
||||
line: function(coordinates, context, winding) {
|
||||
if (!(n = coordinates.length)) return;
|
||||
var point = rotatePoint(coordinates[0]), keepWinding = true, λ0 = point[0], φ0 = point[1], λ1, φ1, sλ0 = λ0 > 0 ? π : -π, sλ1, dλ, i = 0, n;
|
||||
line: function(coordinates, context, ring) {
|
||||
if (!(n = coordinates.length)) return [ ring && 0, false ];
|
||||
var point = rotatePoint(coordinates[0]), λ0 = point[0], φ0 = point[1], λ1, φ1, sλ0 = λ0 > 0 ? π : -π, sλ1, dλ, i = 0, n, clean = ring, area = 0, x0 = (point = d3_geo_stereographic(λ0, φ0))[0], x, y0 = point[1], y;
|
||||
context.moveTo(λ0, φ0);
|
||||
while (++i < n) {
|
||||
point = rotatePoint(coordinates[i]);
|
||||
|
@ -6264,35 +6260,48 @@
|
|||
context.lineTo(sλ0, φ0);
|
||||
context.moveTo(sλ1, φ0);
|
||||
context.lineTo(λ1, φ0);
|
||||
keepWinding = false;
|
||||
clean = false;
|
||||
} else if (sλ0 !== sλ1 && dλ >= π) {
|
||||
if (Math.abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε;
|
||||
if (Math.abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε;
|
||||
φ0 = d3_geo_projectionIntersectAntemeridian(λ0, φ0, λ1, φ1);
|
||||
context.lineTo(sλ0, φ0);
|
||||
context.moveTo(sλ1, φ0);
|
||||
keepWinding = false;
|
||||
clean = false;
|
||||
}
|
||||
if (clean) {
|
||||
x = (point = d3_geo_stereographic(λ1, φ1))[0];
|
||||
y = point[1];
|
||||
area += y0 * x - x0 * y;
|
||||
x0 = x;
|
||||
y0 = y;
|
||||
}
|
||||
context.lineTo(λ0 = λ1, φ0 = φ1);
|
||||
sλ0 = sλ1;
|
||||
}
|
||||
if (winding != null) context.closePath();
|
||||
return keepWinding && winding;
|
||||
return [ clean && area, true ];
|
||||
},
|
||||
polygon: function(polygon, context) {
|
||||
d3_geo_circleClipPolygon(polygon, context, clip.line, d3_geo_antemeridianInterpolate, d3_geo_antemeridianAngle);
|
||||
d3_geo_circleClipPolygon(polygon, context, clip.line, d3_geo_antemeridianInterpolate);
|
||||
}
|
||||
};
|
||||
return clip;
|
||||
}
|
||||
function d3_geo_antemeridianAngle(point) {
|
||||
return -(point[0] < 0 ? point[1] - π / 2 - ε : π / 2 - point[1]);
|
||||
}
|
||||
function d3_geo_antemeridianInterpolate(from, to, direction, context) {
|
||||
from = from.point;
|
||||
to = to.point;
|
||||
if (Math.abs(from[0] - to[0]) > ε) {
|
||||
var s = (from[0] < to[0] ? 1 : -1) * π, φ = direction * s / 2;
|
||||
var φ;
|
||||
if (from == null) {
|
||||
φ = direction * π / 2;
|
||||
context.lineTo(-π, φ);
|
||||
context.lineTo(0, φ);
|
||||
context.lineTo(π, φ);
|
||||
context.lineTo(π, 0);
|
||||
context.lineTo(π, -φ);
|
||||
context.lineTo(0, -φ);
|
||||
context.lineTo(-π, -φ);
|
||||
context.lineTo(-π, 0);
|
||||
} else if (Math.abs(from[0] - to[0]) > ε) {
|
||||
var s = (from[0] < to[0] ? 1 : -1) * π;
|
||||
φ = direction * s / 2;
|
||||
context.lineTo(-s, φ);
|
||||
context.lineTo(0, φ);
|
||||
context.lineTo(s, φ);
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,7 +9,7 @@ d3.geo.circle = function() {
|
|||
var o = typeof origin === "function" ? origin.apply(this, arguments) : origin;
|
||||
rotate = d3_geo_rotation(-o[0] * d3_radians, -o[1] * d3_radians, 0);
|
||||
var ring = [];
|
||||
d3_geo_circleInterpolateCircle(interpolate, {
|
||||
interpolate(null, null, 1, {
|
||||
lineTo: function(λ, φ) {
|
||||
var point = rotate.invert(λ, φ);
|
||||
point[0] *= d3_degrees;
|
||||
|
@ -37,7 +37,7 @@ d3.geo.circle = function() {
|
|||
|
||||
circle.precision = function(_) {
|
||||
if (!arguments.length) return precision;
|
||||
interpolate = d3_geo_circleInterpolate(radians, (precision = +_) * d3_radians);
|
||||
interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians);
|
||||
return circle;
|
||||
};
|
||||
|
||||
|
@ -47,8 +47,6 @@ d3.geo.circle = function() {
|
|||
function d3_geo_circleClip(degrees, rotate) {
|
||||
var radians = degrees * d3_radians,
|
||||
cr = Math.cos(radians),
|
||||
center = [cr, 0, 0], // Cartesian center of the circle.
|
||||
angle = d3_geo_circleAngle(center),
|
||||
interpolate = d3_geo_circleInterpolate(radians, 6 * d3_radians);
|
||||
|
||||
return {
|
||||
|
@ -61,7 +59,7 @@ function d3_geo_circleClip(degrees, rotate) {
|
|||
clipLine(coordinates, context);
|
||||
},
|
||||
polygon: function(polygon, context) {
|
||||
d3_geo_circleClipPolygon(polygon, context, clipLine, interpolate, angle);
|
||||
d3_geo_circleClipPolygon(polygon, context, clipLine, interpolate);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -70,52 +68,67 @@ function d3_geo_circleClip(degrees, rotate) {
|
|||
}
|
||||
|
||||
// TODO two invisible endpoints with visible intermediate segment.
|
||||
function clipLine(coordinates, context, winding) {
|
||||
if (!(n = coordinates.length)) return;
|
||||
function clipLine(coordinates, context, ring) {
|
||||
if (!(n = coordinates.length)) return [ring && 0, false];
|
||||
var point0 = rotate(coordinates[0]),
|
||||
point1,
|
||||
point2,
|
||||
inside = visible(point0),
|
||||
keepWinding = winding != null,
|
||||
closed = keepWinding && inside,
|
||||
v0 = visible(point0),
|
||||
v00 = ring && v0,
|
||||
v,
|
||||
n,
|
||||
move0,
|
||||
line0;
|
||||
if (inside) context.moveTo((move0 = point0)[0], point0[1]);
|
||||
clean = ring, // clean indicates no intersections
|
||||
area = 0,
|
||||
p,
|
||||
x0,
|
||||
x,
|
||||
y0,
|
||||
y;
|
||||
if (clean) {
|
||||
x0 = (p = d3_geo_stereographic(point0[0] + (v0 ? 0 : π), point0[1]))[0];
|
||||
y0 = p[1];
|
||||
}
|
||||
if (v0) context.moveTo(point0[0], point0[1]);
|
||||
for (var i = 1; i < n; i++) {
|
||||
var point1 = rotate(coordinates[i]),
|
||||
point1 = rotate(coordinates[i]);
|
||||
v = visible(point1);
|
||||
// handle degeneracies
|
||||
if (v !== v0) {
|
||||
point2 = intersect(point0, point1);
|
||||
if (pointsEqual(point0, point2) || pointsEqual(point1, point2)) {
|
||||
point1[0] += ε;
|
||||
point1[1] += ε;
|
||||
v = visible(point1);
|
||||
if (v !== inside) {
|
||||
if (inside = v) {
|
||||
// outside going in
|
||||
point2 = intersect(point1, point0);
|
||||
// avoid coincident points
|
||||
if (!line0 || (Math.abs(line0[0] - point2[0]) > ε || Math.abs(line0[1] - point2[1]) > ε)) {
|
||||
if (move0) keepWinding = false;
|
||||
context.moveTo((move0 = point2)[0], point2[1]);
|
||||
}
|
||||
if (keepWinding) winding += d3_geo_circleWinding(point2, point1);
|
||||
point0 = point2;
|
||||
} else {
|
||||
// inside going out
|
||||
line0 = point2 = intersect(point0, point1);
|
||||
context.lineTo(point2[0], point2[1]);
|
||||
// check to see if this generates a closed polygon
|
||||
if (keepWinding) {
|
||||
if (Math.abs(move0[0] - point2[0]) > ε || Math.abs(move0[1] - point2[1]) > ε) {
|
||||
keepWinding = false;
|
||||
} else {
|
||||
winding += d3_geo_circleWinding(point0, move0);
|
||||
}
|
||||
}
|
||||
point0 = point2;
|
||||
}
|
||||
}
|
||||
if (keepWinding) winding += d3_geo_circleWinding(point0, point1);
|
||||
if (v) context.lineTo(point1[0], point1[1]);
|
||||
if (v !== v0) {
|
||||
clean = false;
|
||||
if (v0 = v) {
|
||||
// outside going in
|
||||
point2 = intersect(point1, point0);
|
||||
context.moveTo(point2[0], point2[1]);
|
||||
} else {
|
||||
// inside going out
|
||||
point2 = intersect(point0, point1);
|
||||
context.lineTo(point2[0], point2[1]);
|
||||
}
|
||||
point0 = point2;
|
||||
}
|
||||
if (clean) {
|
||||
p = d3_geo_stereographic(point1[0] + (v ? 0 : π), point1[1]);
|
||||
x = p[0];
|
||||
y = p[1];
|
||||
area += y0 * x - x0 * y;
|
||||
x0 = x;
|
||||
y0 = y;
|
||||
}
|
||||
if (v && !pointsEqual(point0, point1)) context.lineTo(point1[0], point1[1]);
|
||||
point0 = point1;
|
||||
}
|
||||
if (closed && v) context.closePath();
|
||||
return keepWinding && (!move0 || Math.abs(move0[0] - point0[0]) < ε && Math.abs(move0[1] - point0[1]) < ε) && winding;
|
||||
return [
|
||||
clean && area * .5,
|
||||
v00 && v // whether the first and last segments should be rejoined
|
||||
];
|
||||
}
|
||||
|
||||
// Intersects the great circle between a and b with the clip circle.
|
||||
|
@ -154,11 +167,15 @@ function d3_geo_circleInterpolate(radians, precision) {
|
|||
var cr = Math.cos(radians),
|
||||
sr = Math.sin(radians);
|
||||
return function(from, to, direction, context) {
|
||||
var step = direction * precision;
|
||||
from = from.angle;
|
||||
to = to.angle;
|
||||
if (from < to) from += 2 * π;
|
||||
for (var step = precision, t = from; direction > 0 ? t > to : t < to; t -= step) {
|
||||
if (from != null) {
|
||||
from = d3_geo_circleAngle(cr, from);
|
||||
to = d3_geo_circleAngle(cr, to);
|
||||
if (direction > 0 ? from < to: from > to) from += direction * 2 * π;
|
||||
} else {
|
||||
from = radians + direction * 2 * π;
|
||||
to = radians;
|
||||
}
|
||||
for (var step = direction * precision, t = from; direction > 0 ? t > to : t < to; t -= step) {
|
||||
var c = Math.cos(t),
|
||||
s = Math.sin(t),
|
||||
point = d3_geo_circleSpherical([
|
||||
|
@ -171,24 +188,32 @@ function d3_geo_circleInterpolate(radians, precision) {
|
|||
};
|
||||
}
|
||||
|
||||
function d3_geo_circleClipPolygon(coordinates, context, clipLine, interpolate, angle) {
|
||||
function d3_geo_circleClipPolygon(coordinates, context, clipLine, interpolate) {
|
||||
var subject = [],
|
||||
clip = [],
|
||||
segments = [],
|
||||
buffer = d3_geo_circleBufferSegments(clipLine),
|
||||
winding = 0,
|
||||
count = 0;
|
||||
draw = [],
|
||||
visibleArea = 0,
|
||||
invisibleArea = 0,
|
||||
invisible = false;
|
||||
coordinates.forEach(function(ring) {
|
||||
var x = buffer(ring, context),
|
||||
ringSegments = x[1];
|
||||
winding += x[0];
|
||||
var n = ringSegments.length;
|
||||
if (!n) return;
|
||||
count += n;
|
||||
ringSegments = x[1],
|
||||
segment,
|
||||
n = ringSegments.length;
|
||||
|
||||
if (typeof x[0] === "number") {
|
||||
var segment = ringSegments[0],
|
||||
point = segment[0],
|
||||
if (!n) {
|
||||
invisible = true;
|
||||
invisibleArea += x[0][0];
|
||||
return;
|
||||
}
|
||||
|
||||
// No intersections.
|
||||
if (x[0][0] !== false) {
|
||||
visibleArea += x[0][0];
|
||||
draw.push(segment = ringSegments[0]);
|
||||
var point = segment[0],
|
||||
n = segment.length - 1,
|
||||
i = 0;
|
||||
context.moveTo(point[0], point[1]);
|
||||
|
@ -196,28 +221,36 @@ function d3_geo_circleClipPolygon(coordinates, context, clipLine, interpolate, a
|
|||
context.closePath();
|
||||
return;
|
||||
}
|
||||
segments = segments.concat(ringSegments);
|
||||
|
||||
// Rejoin connected segments.
|
||||
if (n > 1 && x[0][1]) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
|
||||
|
||||
segments = segments.concat(ringSegments.filter(d3_geo_circleSegmentLength1));
|
||||
});
|
||||
|
||||
if (count ? winding > 0 : winding < 0) {
|
||||
var moved = false;
|
||||
d3_geo_circleInterpolateCircle(interpolate, {
|
||||
lineTo: function(x, y) {
|
||||
(moved ? context.lineTo : (moved = true, context.moveTo))(x, y);
|
||||
}
|
||||
});
|
||||
context.closePath();
|
||||
if (!segments.length) {
|
||||
if (visibleArea < 0 || invisible && invisibleArea < 0) {
|
||||
var moved = false;
|
||||
interpolate(null, null, 1, {
|
||||
lineTo: function(x, y) {
|
||||
(moved ? context.lineTo : (moved = true, context.moveTo))(x, y);
|
||||
}
|
||||
});
|
||||
context.closePath();
|
||||
}
|
||||
}
|
||||
segments.forEach(function(segment) {
|
||||
var n = segment.length;
|
||||
if (n <= 1) return;
|
||||
var p0 = segment[0],
|
||||
p1 = segment[segment.length - 1],
|
||||
p1 = segment[n - 1],
|
||||
a = {point: p0, points: segment, other: null, visited: false, entry: true, subject: true},
|
||||
b = {point: p0, angle: angle(p0), points: [p0], other: a, visited: false, entry: false, subject: false};
|
||||
b = {point: p0, points: [p0], other: a, visited: false, entry: false, subject: false};
|
||||
a.other = b;
|
||||
subject.push(a);
|
||||
clip.push(b);
|
||||
a = {point: p1, points: [p1], other: null, visited: false, entry: false, subject: true};
|
||||
b = {point: p1, angle: angle(p1), points: [p1], other: a, visited: false, entry: true, subject: false};
|
||||
b = {point: p1, points: [p1], other: a, visited: false, entry: true, subject: false};
|
||||
a.other = b;
|
||||
subject.push(a);
|
||||
clip.push(b);
|
||||
|
@ -244,7 +277,7 @@ function d3_geo_circleClipPolygon(coordinates, context, clipLine, interpolate, a
|
|||
if (current.subject) {
|
||||
for (var i = 0; i < points.length; i++) context.lineTo((point = points[i])[0], point[1]);
|
||||
} else {
|
||||
interpolate(current, current.next, 1, context);
|
||||
interpolate(current.point, current.next.point, 1, context);
|
||||
}
|
||||
current = current.next;
|
||||
} else {
|
||||
|
@ -252,7 +285,7 @@ function d3_geo_circleClipPolygon(coordinates, context, clipLine, interpolate, a
|
|||
points = current.prev.points;
|
||||
for (var i = points.length; --i >= 0;) context.lineTo((point = points[i])[0], point[1]);
|
||||
} else {
|
||||
interpolate(current, current.prev, -1, context);
|
||||
interpolate(current.point, current.prev.point, -1, context);
|
||||
}
|
||||
current = current.prev;
|
||||
}
|
||||
|
@ -271,16 +304,17 @@ function d3_geo_circleLinkCircular(array) {
|
|||
}
|
||||
}
|
||||
|
||||
function d3_geo_circleClipSort(a, b) { return b.angle - a.angle; }
|
||||
function d3_geo_circleClipSort(a, b) {
|
||||
return ((a = a.point)[0] < 0 ? a[1] - π / 2 - ε : π / 2 - a[1])
|
||||
- ((b = b.point)[0] < 0 ? b[1] - π / 2 - ε : π / 2 - b[1]);
|
||||
}
|
||||
|
||||
// Signed angle of a cartesian point relative to [0, 0, 0].
|
||||
function d3_geo_circleAngle(center) {
|
||||
return function(point) {
|
||||
var a = d3_geo_circleCartesian(point, center);
|
||||
d3_geo_circleNormalize(a);
|
||||
var angle = Math.acos(Math.max(-1, Math.min(1, -a[1])));
|
||||
return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI) % (2 * Math.PI);
|
||||
};
|
||||
function d3_geo_circleAngle(cr, point) {
|
||||
var a = d3_geo_circleCartesian(point, [cr, 0, 0]);
|
||||
d3_geo_circleNormalize(a);
|
||||
var angle = Math.acos(Math.max(-1, Math.min(1, -a[1])));
|
||||
return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI);
|
||||
}
|
||||
|
||||
// Convert spherical to normalized Cartesian coordinates, relative to a
|
||||
|
@ -336,40 +370,16 @@ function d3_geo_circleBufferSegments(f) {
|
|||
segment;
|
||||
return [
|
||||
f(coordinates, {
|
||||
point: d3_noop,
|
||||
moveTo: function(x, y) { segments.push(segment = [[x, y]]); },
|
||||
lineTo: function(x, y) { segment.push([x, y]); },
|
||||
closePath: function() {
|
||||
if (segments.length < 2) return;
|
||||
segments.pop();
|
||||
segments.push(segment = segment.concat(segments.shift()));
|
||||
}
|
||||
}, 0),
|
||||
lineTo: function(x, y) { segment.push([x, y]); }
|
||||
}, true),
|
||||
segments
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
// Based on http://softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm
|
||||
// Calculates a single winding number step relative to [0, 0].
|
||||
// TODO optimise stereographic calculation using 3D coordinates.
|
||||
function d3_geo_circleWinding(p0, p) {
|
||||
var c0 = Math.cos(p0[1]),
|
||||
k0 = 1 + Math.cos(p0[0]) * c0,
|
||||
c = Math.cos(p[1]),
|
||||
k = 1 + Math.cos(p[0]) * c;
|
||||
p0 = [c0 * Math.sin(p0[0]) / k0, Math.sin(p0[1]) / k0];
|
||||
p = [c * Math.sin(p[0]) / k, Math.sin(p[1]) / k];
|
||||
if (p0[1] <= 0) {
|
||||
if (p[1] > 0 && (p0[0] - p[0]) * p0[1] + p0[0] * (p[1] - p0[1]) > 0) return 1;
|
||||
} else {
|
||||
if (p[1] <= 0 && (p0[0] - p[0]) * p0[1] + p0[0] * (p[1] - p0[1]) < 0) return -1;
|
||||
}
|
||||
return 0;
|
||||
function pointsEqual(a, b) {
|
||||
return Math.abs(a[0] - b[0]) < ε && Math.abs(a[1] - b[1]) < ε;
|
||||
}
|
||||
|
||||
function d3_geo_circleInterpolateCircle(interpolate, context) {
|
||||
for (var i = 0; i < 4; i++) {
|
||||
interpolate({angle: -i * π / 2}, {angle: -(i + 1) * π / 2}, 1, context);
|
||||
}
|
||||
}
|
||||
function d3_geo_circleSegmentLength1(segment) { return segment.length > 1; }
|
||||
|
|
|
@ -196,10 +196,9 @@ function d3_geo_projectionCutAntemeridian(rotatePoint) {
|
|||
var point = rotatePoint(coordinates);
|
||||
context.point(point[0], point[1]);
|
||||
},
|
||||
line: function(coordinates, context, winding) {
|
||||
if (!(n = coordinates.length)) return;
|
||||
line: function(coordinates, context, ring) {
|
||||
if (!(n = coordinates.length)) return [ring && 0, false];
|
||||
var point = rotatePoint(coordinates[0]),
|
||||
keepWinding = true,
|
||||
λ0 = point[0],
|
||||
φ0 = point[1],
|
||||
λ1,
|
||||
|
@ -208,7 +207,13 @@ function d3_geo_projectionCutAntemeridian(rotatePoint) {
|
|||
sλ1,
|
||||
dλ,
|
||||
i = 0,
|
||||
n;
|
||||
n,
|
||||
clean = ring, // clean indicates no intersections
|
||||
area = 0,
|
||||
x0 = (point = d3_geo_stereographic(λ0, φ0))[0],
|
||||
x,
|
||||
y0 = point[1],
|
||||
y;
|
||||
context.moveTo(λ0, φ0);
|
||||
while (++i < n) {
|
||||
point = rotatePoint(coordinates[i]);
|
||||
|
@ -221,7 +226,7 @@ function d3_geo_projectionCutAntemeridian(rotatePoint) {
|
|||
context.lineTo(sλ0, φ0);
|
||||
context.moveTo(sλ1, φ0);
|
||||
context.lineTo(λ1, φ0);
|
||||
keepWinding = false;
|
||||
clean = false;
|
||||
} else if (sλ0 !== sλ1 && dλ >= π) { // line crosses antemeridian
|
||||
// handle degeneracies
|
||||
if (Math.abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε;
|
||||
|
@ -229,31 +234,42 @@ function d3_geo_projectionCutAntemeridian(rotatePoint) {
|
|||
φ0 = d3_geo_projectionIntersectAntemeridian(λ0, φ0, λ1, φ1);
|
||||
context.lineTo(sλ0, φ0);
|
||||
context.moveTo(sλ1, φ0);
|
||||
keepWinding = false;
|
||||
clean = false;
|
||||
}
|
||||
if (clean) {
|
||||
x = (point = d3_geo_stereographic(λ1, φ1))[0];
|
||||
y = point[1];
|
||||
area += y0 * x - x0 * y;
|
||||
x0 = x;
|
||||
y0 = y;
|
||||
}
|
||||
context.lineTo(λ0 = λ1, φ0 = φ1);
|
||||
sλ0 = sλ1;
|
||||
}
|
||||
if (winding != null) context.closePath();
|
||||
return keepWinding && winding;
|
||||
return [clean && area, true];
|
||||
},
|
||||
polygon: function(polygon, context) {
|
||||
d3_geo_circleClipPolygon(polygon, context, clip.line, d3_geo_antemeridianInterpolate, d3_geo_antemeridianAngle);
|
||||
d3_geo_circleClipPolygon(polygon, context, clip.line, d3_geo_antemeridianInterpolate);
|
||||
}
|
||||
};
|
||||
return clip;
|
||||
}
|
||||
|
||||
function d3_geo_antemeridianAngle(point) {
|
||||
return -(point[0] < 0 ? point[1] - π / 2 - ε : π / 2 - point[1]);
|
||||
}
|
||||
|
||||
function d3_geo_antemeridianInterpolate(from, to, direction, context) {
|
||||
from = from.point;
|
||||
to = to.point;
|
||||
if (Math.abs(from[0] - to[0]) > ε) {
|
||||
var s = (from[0] < to[0] ? 1 : -1) * π,
|
||||
φ = direction * s / 2;
|
||||
var φ;
|
||||
if (from == null) {
|
||||
φ = direction * π / 2;
|
||||
context.lineTo(-π, φ);
|
||||
context.lineTo( 0, φ);
|
||||
context.lineTo( π, φ);
|
||||
context.lineTo( π, 0);
|
||||
context.lineTo( π, -φ);
|
||||
context.lineTo( 0, -φ);
|
||||
context.lineTo(-π, -φ);
|
||||
context.lineTo(-π, 0);
|
||||
} else if (Math.abs(from[0] - to[0]) > ε) {
|
||||
var s = (from[0] < to[0] ? 1 : -1) * π;
|
||||
φ = direction * s / 2;
|
||||
context.lineTo(-s, φ);
|
||||
context.lineTo( 0, φ);
|
||||
context.lineTo( s, φ);
|
||||
|
|
|
@ -11,7 +11,7 @@ suite.addBatch({
|
|||
"generates a Polygon": function(circle) {
|
||||
var o = circle();
|
||||
assert.equal(o.type, "Polygon");
|
||||
assert.inDelta(o.coordinates, [[[-90, 0], [-90, 6], [-90, 12], [-90, 18], [-90, 24], [-90, 30], [-90, 36], [-90, 42], [-90, 48], [-90, 54], [-90, 60], [-90, 66], [-90, 72], [-90, 78], [-90, 84], [-77.803070, 90], [-45, 90], [90, 84], [90, 78], [90, 72], [90, 66], [90, 60], [90, 54], [90, 48], [90, 42], [90, 36], [90, 30], [90, 24], [90, 18], [90, 12], [90, 6], [90, 0], [90, -6], [90, -12], [90, -18], [90, -24], [90, -30], [90, -36], [90, -42], [90, -48], [90, -54], [90, -60], [90, -66], [90, -72], [90, -78], [90, -84], [86.730543, -90], [71.565051, -90], [-90, -84], [-90, -78], [-90, -72], [-90, -66], [-90, -60], [-90, -54], [-90, -48], [-90, -42], [-90, -36], [-90, -30], [-90, -24], [-90, -18], [-90, -12], [-90, -6], [-90, 0]]], 1e-6);
|
||||
assert.inDelta(o.coordinates, [[[-78.690067, -90], [-90, -84], [-90, -78], [-90, -72], [-90, -66], [-90, -60], [-90, -54], [-90, -48], [-90, -42], [-90, -36], [-90, -30], [-90, -24], [-90, -18], [-90, -12], [-90, -6], [-90, 0], [-90, 6], [-90, 12], [-90, 18], [-90, 24], [-90, 30], [-90, 36], [-90, 42], [-90, 48], [-90, 54], [-90, 60], [-90, 66], [-90, 72], [-90, 78], [-90, 84], [-89.596672, 90], [90, 84], [90, 78], [90, 72], [90, 66], [90, 60], [90, 54], [90, 48], [90, 42], [90, 36], [90, 30], [90, 24], [90, 18], [90, 12], [90, 6], [90, 0], [90, -6], [90, -12], [90, -18], [90, -24], [90, -30], [90, -36], [90, -42], [90, -48], [90, -54], [90, -60], [90, -66], [90, -72], [90, -78], [90, -84], [89.569782, -90]]], 1e-6);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Загрузка…
Ссылка в новой задаче