d3-jsnext
Version:
d3, but futuristic
152 lines (132 loc) • 4.51 kB
JavaScript
import { halfπ, ε } from '../math/trigonometry';
import { d3_noop } from '../core/noop';
import { d3_geo_clipPolygon } from './clip-polygon';
import { d3_geo_pointInPolygon } from './point-in-polygon';
import { d3$merge } from '../arrays/merge';
function d3_geo_clip(pointVisible, clipLine, interpolate, clipStart) {
return function(rotate, listener) {
var line = clipLine(listener),
rotatedClipStart = rotate.invert(clipStart[0], clipStart[1]);
var clip = {
point: point,
lineStart: lineStart,
lineEnd: lineEnd,
polygonStart: function() {
clip.point = pointRing;
clip.lineStart = ringStart;
clip.lineEnd = ringEnd;
segments = [];
polygon = [];
},
polygonEnd: function() {
clip.point = point;
clip.lineStart = lineStart;
clip.lineEnd = lineEnd;
segments = d3$merge(segments);
var clipStartInside = d3_geo_pointInPolygon(rotatedClipStart, polygon);
if (segments.length) {
if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
d3_geo_clipPolygon(segments, d3_geo_clipSort, clipStartInside, interpolate, listener);
} else if (clipStartInside) {
if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
listener.lineStart();
interpolate(null, null, 1, listener);
listener.lineEnd();
}
if (polygonStarted) listener.polygonEnd(), polygonStarted = false;
segments = polygon = null;
},
sphere: function() {
listener.polygonStart();
listener.lineStart();
interpolate(null, null, 1, listener);
listener.lineEnd();
listener.polygonEnd();
}
};
function point(λ, φ) {
var point = rotate(λ, φ);
if (pointVisible(λ = point[0], φ = point[1])) listener.point(λ, φ);
}
function pointLine(λ, φ) {
var point = rotate(λ, φ);
line.point(point[0], point[1]);
}
function lineStart() { clip.point = pointLine; line.lineStart(); }
function lineEnd() { clip.point = point; line.lineEnd(); }
var segments;
var buffer = d3_geo_clipBufferListener(),
ringListener = clipLine(buffer),
polygonStarted = false,
polygon,
ring;
function pointRing(λ, φ) {
ring.push([λ, φ]);
var point = rotate(λ, φ);
ringListener.point(point[0], point[1]);
}
function ringStart() {
ringListener.lineStart();
ring = [];
}
function ringEnd() {
pointRing(ring[0][0], ring[0][1]);
ringListener.lineEnd();
var clean = ringListener.clean(),
ringSegments = buffer.buffer(),
segment,
n = ringSegments.length;
ring.pop();
polygon.push(ring);
ring = null;
if (!n) return;
// No intersections.
if (clean & 1) {
segment = ringSegments[0];
var n = segment.length - 1,
i = -1,
point;
if (n > 0) {
if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
listener.lineStart();
while (++i < n) listener.point((point = segment[i])[0], point[1]);
listener.lineEnd();
}
return;
}
// Rejoin connected segments.
// TODO reuse bufferListener.rejoin()?
if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
segments.push(ringSegments.filter(d3_geo_clipSegmentLength1));
}
return clip;
};
}
function d3_geo_clipSegmentLength1(segment) {
return segment.length > 1;
}
function d3_geo_clipBufferListener() {
var lines = [],
line;
return {
lineStart: function() { lines.push(line = []); },
point: function(λ, φ) { line.push([λ, φ]); },
lineEnd: d3_noop,
buffer: function() {
var buffer = lines;
lines = [];
line = null;
return buffer;
},
rejoin: function() {
if (lines.length > 1) lines.push(lines.pop().concat(lines.shift()));
}
};
}
// Intersection points are sorted along the clip edge. For both antimeridian
// cutting and circle clipping, the same comparison is used.
function d3_geo_clipSort(a, b) {
return ((a = a.x)[0] < 0 ? a[1] - halfπ - ε : halfπ - a[1])
- ((b = b.x)[0] < 0 ? b[1] - halfπ - ε : halfπ - b[1]);
}
export { d3_geo_clipSort, d3_geo_clipBufferListener, d3_geo_clipSegmentLength1, d3_geo_clip };