import Point from '../../geo/Point'; import Coordinate from '../../geo/Coordinate'; import { isNumber } from './common'; import { PointExtent } from '../../geo'; export function clipLine(points, bounds, round?: boolean, noCut?: boolean) { const min = bounds.getMin(), max = bounds.getMax(); const parts = []; let k = 0, segment; for (let j = 0, l = points.length; j < l - 1; j++) { segment = clipSegment(points[j], points[j + 1], min, max, j, round, noCut); if (!segment) { continue; } parts[k] = parts[k] || []; parts[k].push({ 'point': segment[0], 'index': j }); // if segment goes out of screen, or it's the last one, it's the end of the line part if ((segment[1] !== points[j + 1]) || (j === l - 2)) { // parts[k].push(segment[1]); parts[k].push({ 'point': segment[1], 'index': j + 1 }); k++; } } return parts; } let _lastCode; // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean // Clips the segment a to b by rectangular bounds with the // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm) // (modifying the segment points directly!). Used by Leaflet to only show polyline // points that are on the screen or near, increasing performance. // @copyright Leaflet export function clipSegment(a, b, min: Point, max: Point, useLastCode, round, noCut) { let codeA = useLastCode ? _lastCode : _getBitCode(a, min, max), codeB = _getBitCode(b, min, max), codeOut, p, newCode; // save 2nd code to avoid calculating it on the next segment //@internal _lastCode = codeB; /* eslint-disable no-constant-condition */ while (true) { // if a,b is inside the clip window (trivial accept) if (!(codeA | codeB)) { return [a, b]; } // if a,b is outside the clip window (trivial reject) if (codeA & codeB) { return false; } if (noCut) { return [a, b]; } // other cases codeOut = codeA || codeB; p = _getEdgeIntersection(a, b, codeOut, min, max, round); newCode = _getBitCode(p, min, max); if (codeOut === codeA) { a = p; codeA = newCode; } else { b = p; codeB = newCode; } } /* eslint-enable no-constant-condition */ } /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[] * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgeman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)). * Used by Leaflet to only show polygon points that are on the screen or near, increasing * performance. Note that polygon points needs different algorithm for clipping * than polyline, so there's a seperate method for it. * @copyright Leaflet */ export function clipPolygon(points, bounds: PointExtent, round?: boolean) { const edges = [1, 4, 2, 8]; let clippedPoints, i, j, k, a, b, len, edge, p; const min = bounds.getMin(), max = bounds.getMax(); for (i = 0, len = points.length; i < len; i++) { points[i]._code = _getBitCode(points[i], min, max); } // for each edge (left, bottom, right, top) for (k = 0; k < 4; k++) { edge = edges[k]; clippedPoints = []; for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { a = points[i]; b = points[j]; // if a is inside the clip window if (!(a._code & edge)) { // if b is outside the clip window (a->b goes out of screen) if (b._code & edge) { p = _getEdgeIntersection(b, a, edge, min, max, round); p._code = _getBitCode(p, min, max); clippedPoints.push(p); } clippedPoints.push(a); // else if b is inside the clip window (a->b enters the screen) } else if (!(b._code & edge)) { p = _getEdgeIntersection(b, a, edge, min, max, round); p._code = _getBitCode(p, min, max); clippedPoints.push(p); } } points = clippedPoints; } return points; } /** * caculate the distance from a point to a segment. * @param p * @param p1 * @param p2 * @return distance from p to (p1, p2) * @memberOf Util */ export function distanceToSegment(p: Point, p1: Point, p2: Point) { const x = p.x, y = p.y, x1 = p1.x, y1 = p1.y, x2 = p2.x, y2 = p2.y; const cross = (x2 - x1) * (x - x1) + (y2 - y1) * (y - y1); if (cross <= 0) { // P->P1 return Math.sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1)); } const d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); if (cross >= d2) { // P->P2 return Math.sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2)); } const r = cross / d2; const px = x1 + (x2 - x1) * r; const py = y1 + (y2 - y1) * r; // P->P(px,py) return Math.sqrt((x - px) * (x - px) + (y - py) * (y - py)); } /** * Whether the coordinate is inside the polygon * @param p * @param points * @return * @memberOf Util */ export function pointInsidePolygon(p: Coordinate, points: Coordinate[]): boolean { let p1, p2; const len = points.length; let c = false; for (let i = 0, j = len - 1; i < len; j = i++) { p1 = points[i]; p2 = points[j]; if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) { c = !c; } } return c; } function _getEdgeIntersection(a, b, code, min: Point, max: Point, round) { const dx = b.x - a.x, dy = b.y - a.y; let x, y; if (code & 8) { // top x = a.x + dx * (max.y - a.y) / dy; y = max.y; } else if (code & 4) { // bottom x = a.x + dx * (min.y - a.y) / dy; y = min.y; } else if (code & 2) { // right x = max.x; y = a.y + dy * (max.x - a.x) / dx; } else if (code & 1) { // left x = min.x; y = a.y + dy * (min.x - a.x) / dx; } const p = new Point(x, y); if (round) { p._round(); } return p; } function _getBitCode(p, min: Point, max: Point) { let code = 0; if (p.x < min.x) { // left code |= 1; } else if (p.x > max.x) { // right code |= 2; } if (p.y < min.y) { // bottom code |= 4; } else if (p.y > max.y) { // top code |= 8; } return code; } /** * Is the point within an ellipse * @param point * @param center ellipse's center * @param southeast ellipse's southeast point * @param tolerance * @returns * @private * @memberOf Util */ export function withInEllipse(point: Point, center: Point, southeast: Point, tolerance: number) { point = new Point(point); const a = Math.abs(southeast.x - center.x), b = Math.abs(southeast.y - center.y), c = Math.sqrt(Math.abs(a * a - b * b)), xfocus = a >= b; let f1, f2, d; if (xfocus) { f1 = new Point(center.x - c, center.y); f2 = new Point(center.x + c, center.y); d = a * 2; } else { f1 = new Point(center.x, center.y - c); f2 = new Point(center.x, center.y + c); d = b * 2; } /* L1 + L2 = D L1 + t >= L1' L2 + t >= L2' D + 2t >= L1' + L2' */ return (point.distanceTo(f1) + point.distanceTo(f2)) <= (d + 2 * tolerance); } export function getMinMaxAltitude(altitude: number | number[] | number[][] | number[][][]): [number, number] { if (!altitude) { return [0, 0]; } let min = Infinity, max = -Infinity; //number if (isNumber(altitude)) { min = max = altitude; return [min, max]; } const pathMinMax = (alts: number[]) => { for (let i = 0, len = alts.length; i < len; i++) { const alt = alts[i]; min = Math.min(min, alt); max = Math.max(max, alt); } } //number [],line if (!Array.isArray(altitude[0])) { pathMinMax(altitude as number[]); return [min, max]; } //number [][],multiline ,polygon if (!Array.isArray(altitude[0][0])) { for (let i = 0, len = altitude.length; i < len; i++) { const alts = altitude[i]; pathMinMax(alts as number[]); } return [min, max]; } //number [][][],multipolygon for (let i = 0, len = altitude.length; i < len; i++) { const alts = altitude[i] as number[][]; for (let j = 0, len1 = alts.length; j < len1; j++) { const alt = alts[j]; pathMinMax(alt as number[]); } } return [min, max]; } /** * point left segment * @param p * @param p1 * @param p2 * @returns */ export function pointLeftSegment(p: Point, p1: Point, p2: Point) { const x1 = p1.x, y1 = p1.y; const x2 = p2.x, y2 = p2.y; const x = p.x, y = p.y; return (y1 - y2) * x + (x2 - x1) * y + x1 * y2 - x2 * y1 > 0; } function pointRightSegment(p: Point, p1: Point, p2: Point) { return !pointLeftSegment(p, p1, p2); } /** * * LT--------------------RT * \ / * \ / * \ / * LB-----------RB * camera behind * * Points within a convex quadrilateral * @param point * @param p1 * @param p2 * @param p3 * @param p4 * @returns */ export function pointInQuadrilateral(p: Point, LT: Point, RT: Point, RB: Point, LB: Point) { //LT-RT const a = pointRightSegment(p, LT, RT); //RT-RB const b = pointRightSegment(p, RT, RB); //RB-LB const c = pointRightSegment(p, RB, LB); //LB-LT const d = pointRightSegment(p, LB, LT); return a && b && c && d; } /** * 直线和直线的交点 * Intersection of two line * @param p1 * @param p2 * @param p3 * @param p4 * @returns */ export function lineIntersection(p1: Point, p2: Point, p3: Point, p4: Point): Point | null { const dx1 = p2.x - p1.x, dy1 = p2.y - p1.y; const dx2 = p4.x - p3.x, dy2 = p4.y - p3.y; //vertical if (dx1 === 0 && dx2 === 0) { return null; } //horizontal if (dy1 === 0 && dy2 === 0) { return null; } const k1 = dy1 / dx1; const k2 = dy2 / dx2; //Parallel lines if (Math.abs(k1) === Math.abs(k2)) { return null; } const b1 = p1.y - k1 * p1.x; const b2 = p3.y - k2 * p3.x; let x, y; if (dx1 === 0) { x = p1.x; y = k2 * x + b2; } else if (dx2 === 0) { x = p3.x; y = k1 * x + b1; } else if (dy1 === 0) { y = p1.y; x = (y - b2) / k2; } else if (dy2 === 0) { y = p3.y; x = (y - b1) / k1; } else { x = (b2 - b1) / (k1 - k2); y = k1 * x + b1; } return new Point(x, y); } /** * Does it contain altitude values * @param altitudes * @returns */ export function altitudesHasData(altitudes: number | Array) { if (isNumber(altitudes)) { return altitudes !== 0; } else if (Array.isArray(altitudes) && altitudes.length > 0) { for (let i = 0, len = altitudes.length; i < len; i++) { if (isNumber(altitudes[i]) && altitudes[i] !== 0) { return true; } } } return false; }