UNPKG

svg-getpointatlength

Version:

alternative to native pointAtLength() and getTotalLength() method

257 lines (209 loc) 7.37 kB
//import { splitSubpaths } from "./convert_segments"; import { pointAtT, svgArcToCenterParam, getBezierExtremeT, getArcExtemes, getDistance, interpolate, getPointOnEllipse, reducePoints, getSquareDistance } from "./geometry"; import { renderPoint } from "./visualize"; //import {arcToBezier} from'./pathData_convert'; /** * check whether a polygon is likely * to be closed * or an open polyline */ export function isClosedPolygon(pts, reduce = 24) { let ptsR = reducePoints(pts, reduce); let { width, height } = getPolyBBox(ptsR); //let dimAvg = Math.max(width, height); let dimAvg = (width + height) / 2; //let closingThresh = (dimAvg / pts.length) ** 2 let closingThresh = (dimAvg) ** 2 let closingDist = getSquareDistance(pts[0], pts[pts.length - 1]); return closingDist < closingThresh; } /** * calculate polygon bbox */ export function getPolyBBox(vertices, decimals = -1) { let xArr = vertices.map(pt => pt.x); let yArr = vertices.map(pt => pt.y); let left = Math.min(...xArr) let right = Math.max(...xArr) let top = Math.min(...yArr) let bottom = Math.max(...yArr) let bb = { x: left, left: left, right: right, y: top, top: top, bottom: bottom, width: right - left, height: bottom - top }; // round if (decimals > -1) { for (let prop in bb) { bb[prop] = +bb[prop].toFixed(decimals) } } //console.log(bb); return bb; } export function getSubPathBBoxes(subPaths) { let bboxArr = []; subPaths.forEach((pathData) => { //let bb = getPathDataBBox(pathData) let bb = getPathDataBBox_sloppy(pathData); bboxArr.push(bb); }); //console.log('bboxArr', bboxArr); return bboxArr; } export function checkBBoxIntersections(bb, bb1) { let [x, y, width, height, right, bottom] = [ bb.x, bb.y, bb.width, bb.height, bb.x + bb.width, bb.y + bb.height ]; let [x1, y1, width1, height1, right1, bottom1] = [ bb1.x, bb1.y, bb1.width, bb1.height, bb1.x + bb1.width, bb1.y + bb1.height ]; let intersects = false; if (width * height != width1 * height1) { if (width * height > width1 * height1) { if (x < x1 && right > right1 && y < y1 && bottom > bottom1) { intersects = true; } } } return intersects; } /** * sloppy path bbox aaproximation */ export function getPathDataBBox_sloppy(pathData) { let pts = getPathDataPoly(pathData); let bb = getPolyBBox(pts); return bb; } /** * get path data poly * including command points * handy for faster/sloppy bbox approximations */ export function getPathDataPoly(pathData) { let poly = []; for (let i = 0; i < pathData.length; i++) { let com = pathData[i] let prev = i > 0 ? pathData[i - 1] : pathData[i]; let { type, values } = com; let p0 = { x: prev.values[prev.values.length - 2], y: prev.values[prev.values.length - 1] }; let p = values.length ? { x: values[values.length - 2], y: values[values.length - 1] } : '' let cp1 = values.length ? { x: values[0], y: values[1] } : '' switch (type) { // convert to cubic to get polygon case 'A': let [rx, ry, xAxisRotation, largeArc, sweep, x1, y1] = values let isEllipse = rx!==ry; //console.log('isEllipse', isEllipse); if(!isEllipse){ let dx = p.x-p0.x; let dy = p.y-p0.y; let cx = p0.x + dx/2 let cy = p0.y + dy/2 let horizontal = dy === 0; let vertical = dx === 0; let isSemiCircle = (horizontal && !vertical) || (!horizontal && vertical) ; //let isSemiCircle = horizontal || vertical; //console.log('!!isSemiCircle', isSemiCircle, horizontal, vertical, 'dx', dx, dy, 'r', rx, ry); if(isSemiCircle){ let r = horizontal ? dx*0.5 : dy*0.5; r = sweep ? r : -r; let c2 = horizontal ? {x:cx, y:cy-r} : {x:cx-r, y:cy} //renderPoint(svg, c) //renderPoint(markers, c2, 'magenta') poly.push(c2) //console.log('c2', p); //console.log('isSemiCircle', isSemiCircle, dx, dy, horizontal, vertical, 'r', r); } } //console.log('pt', p, 'c2', p); break; case 'C': let cp2 = { x: values[2], y: values[3] } poly.push(cp1, cp2) break; case 'Q': poly.push(cp1) break; } // M and L commands if (type.toLowerCase() !== 'z') { poly.push(p) } } //console.log('poly', poly); return poly; } /** * get exact path BBox * calculating extremes for all command types */ export function getPathDataBBox(pathData) { // save extreme values let xMin = Infinity; let xMax = -Infinity; let yMin = Infinity; let yMax = -Infinity; const setXYmaxMin = (pt) => { if (pt.x < xMin) { xMin = pt.x } if (pt.x > xMax) { xMax = pt.x } if (pt.y < yMin) { yMin = pt.y } if (pt.y > yMax) { yMax = pt.y } } for (let i = 0; i < pathData.length; i++) { let com = pathData[i] let { type, values } = com; let valuesL = values.length; let comPrev = pathData[i - 1] ? pathData[i - 1] : pathData[i]; let valuesPrev = comPrev.values; let valuesPrevL = valuesPrev.length; if (valuesL) { let p0 = { x: valuesPrev[valuesPrevL - 2], y: valuesPrev[valuesPrevL - 1] }; let p = { x: values[valuesL - 2], y: values[valuesL - 1] }; // add final on path point setXYmaxMin(p) if (type === 'C' || type === 'Q') { let cp1 = { x: values[0], y: values[1] }; let cp2 = type === 'C' ? { x: values[2], y: values[3] } : cp1; let pts = type === 'C' ? [p0, cp1, cp2, p] : [p0, cp1, p]; let bezierExtremesT = getBezierExtremeT(pts) bezierExtremesT.forEach(t => { let pt = pointAtT(pts, t); setXYmaxMin(pt) }) } else if (type === 'A') { let arcExtremes = getArcExtemes(p0, values) arcExtremes.forEach(pt => { setXYmaxMin(pt) }) } } } let bbox = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin } return bbox }