UNPKG

d3-geo-polygon

Version:

Clipping and geometric operations for spherical polygons.

125 lines (105 loc) 3.52 kB
import clipBuffer from "./buffer.js"; import clipRejoin from "./rejoin.js"; import {epsilon, halfPi} from "../math.js"; import polygonContains from "../polygonContains.js"; import {merge} from "d3-array"; export default function(pointVisible, clipLine, interpolate, start, sort, {clipPoint = false} = {}) { if (typeof sort === "undefined") sort = compareIntersection; return function(sink) { const line = clipLine(sink); const ringBuffer = clipBuffer(); const ringSink = clipLine(ringBuffer); let polygonStarted = false, polygon, segments, ring; const clip = { point, lineStart, 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 = merge(segments); const startInside = polygonContains(polygon, start); if (segments.length) { if (!polygonStarted) sink.polygonStart(), polygonStarted = true; clipRejoin(segments, sort, startInside, interpolate, sink); } else if (startInside) { if (!polygonStarted) sink.polygonStart(), polygonStarted = true; interpolate(null, null, 1, sink); } if (polygonStarted) sink.polygonEnd(), polygonStarted = false; segments = polygon = null; }, sphere: () => interpolate(null, null, 1, sink) }; function point(lambda, phi) { if ((!clipPoint && !ring) || pointVisible(lambda, phi)) sink.point(lambda, phi); } function pointLine(lambda, phi) { line.point(lambda, phi); } function lineStart() { clip.point = pointLine; line.lineStart(); } function lineEnd() { clip.point = point; line.lineEnd(); } function pointRing(lambda, phi, close) { ring.push([lambda, phi]); ringSink.point(lambda, phi, close); } function ringStart() { ringSink.lineStart(); ring = []; } function ringEnd() { pointRing(ring[0][0], ring[0][1], true); ringSink.lineEnd(); const clean = ringSink.clean(); const ringSegments = ringBuffer.result(); const n = ringSegments.length; let m, segment, point; ring.pop(); polygon.push(ring); ring = null; if (!n) return; // No intersections. if (clean & 1) { segment = ringSegments[0]; if ((m = segment.length - 1) > 0) { if (!polygonStarted) sink.polygonStart(), polygonStarted = true; sink.lineStart(); for (let i = 0; i < m; ++i) sink.point((point = segment[i])[0], point[1]); sink.lineEnd(); } return; } // Rejoin connected segments. // TODO reuse ringBuffer.rejoin()? if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); segments.push(ringSegments.filter(validSegment)); } return clip; }; } function validSegment(segment) { return segment.length > 1; } // Intersections are sorted along the clip edge. For both antimeridian cutting // and circle clipping, the same comparison is used. function compareIntersection(a, b) { return ((a = a.x)[0] < 0 ? a[1] - halfPi - epsilon : halfPi - a[1]) - ((b = b.x)[0] < 0 ? b[1] - halfPi - epsilon : halfPi - b[1]); }