UNPKG

transitive-js

Version:

A tool for generating dynamic stylized transit maps that are easy to understand.

235 lines (197 loc) 4.97 kB
/** * General Transitive utilities library */ import SphericalMercator from 'sphericalmercator' const TOLERANCE = 0.000001 function fuzzyEquals(a, b, tolerance = TOLERANCE) { return Math.abs(a - b) < tolerance } function distance(x1, y1, x2, y2) { return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) } function getRadiusFromAngleChord(angleR, chordLen) { return chordLen / 2 / Math.sin(angleR / 2) } /* * CCW utility function. Accepts 3 coord pairs; result is positive if points * have counterclockwise orientation, negative if clockwise, 0 if collinear. */ function ccw(ax, ay, bx, by, cx, cy) { const raw = ccwRaw(ax, ay, bx, by, cx, cy) return raw === 0 ? 0 : raw / Math.abs(raw) } function ccwRaw(ax, ay, bx, by, cx, cy) { return (bx - ax) * (cy - ay) - (cx - ax) * (by - ay) } /* * Compute angle formed by three points in cartesian plane using law of cosines */ function angleFromThreePoints(ax, ay, bx, by, cx, cy) { const c = distance(ax, ay, bx, by) const a = distance(bx, by, cx, cy) const b = distance(ax, ay, cx, cy) return Math.acos((a * a + c * c - b * b) / (2 * a * c)) } function pointAlongArc(x1, y1, x2, y2, r, theta, ccw, t) { ccw = Math.abs(ccw) / ccw // convert to 1 or -1 let rot = Math.PI / 2 - Math.abs(theta) / 2 const vectToCenter = normalizeVector( rotateVector( { x: x2 - x1, y: y2 - y1 }, ccw * rot ) ) // calculate the center of the arc circle const cx = x1 + r * vectToCenter.x const cy = y1 + r * vectToCenter.y let vectFromCenter = negateVector(vectToCenter) rot = Math.abs(theta) * t * ccw vectFromCenter = normalizeVector(rotateVector(vectFromCenter, rot)) return { x: cx + r * vectFromCenter.x, y: cy + r * vectFromCenter.y } } function getVectorAngle(x, y) { let t = Math.atan(y / x) if (x < 0 && t <= 0) t += Math.PI else if (x < 0 && t >= 0) t -= Math.PI return t } function rayIntersection(ax, ay, avx, avy, bx, by, bvx, bvy) { const u = ((by - ay) * bvx - (bx - ax) * bvy) / (bvx * avy - bvy * avx) const v = ((by - ay) * avx - (bx - ax) * avy) / (bvx * avy - bvy * avx) return { intersect: u > -TOLERANCE && v > -TOLERANCE, u: u, v: v } } function lineIntersection(x1, y1, x2, y2, x3, y3, x4, y4) { const d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) if (d === 0) { // lines are parallel return { intersect: false } } return { intersect: true, x: ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d, y: ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d } } /** * Parse a pixel-based style descriptor, returning an number. * * @param {String/Number} */ function parsePixelStyle(descriptor) { if (typeof descriptor === 'number') return descriptor return parseFloat(descriptor.substring(0, descriptor.length - 2), 10) } /** * Whether vector is projected into positive xy quadrant. */ function isOutwardVector(vector) { return !fuzzyEquals(vector.x, 0) ? vector.x > 0 : vector.y > 0 } /** * vector utilities */ function normalizeVector(v) { const d = Math.sqrt(v.x * v.x + v.y * v.y) return { x: v.x / d, y: v.y / d } } function rotateVector(v, theta) { return { x: v.x * Math.cos(theta) - v.y * Math.sin(theta), y: v.x * Math.sin(theta) + v.y * Math.cos(theta) } } function negateVector(v) { return { x: -v.x, y: -v.y } } function addVectors(v1, v2) { return { x: v1.x + v2.x, y: v1.y + v2.y } } /** * GTFS utilities */ function otpModeToGtfsType(otpMode) { switch (otpMode) { case 'TRAM': return 0 case 'SUBWAY': return 1 case 'RAIL': return 2 case 'BUS': return 3 case 'FERRY': return 4 case 'CABLE_CAR': return 5 case 'GONDOLA': return 6 case 'FUNICULAR': return 7 } } // Rendering utilities function renderDataToSvgPath(renderData) { return renderData .map((d, k) => { if (k === 0) return `M${d.x} ${d.y}` if (d.arc) { return `A${d.radius} ${d.radius} ${d.arc} 0 ${d.arc > 0 ? 0 : 1} ${ d.x } ${d.y}` } return `L${d.x} ${d.y}` }) .join(' ') } // An instance of the SphericalMercator converter const sm = new SphericalMercator() /** * @param {*} fontSize A CSS font size or a numerical (pixel) font size. * @returns A CSS font size ending with the provided CSS unit or 'px' if none provided. */ function getFontSizeWithUnit(fontSize) { return fontSize + (isFinite(fontSize) ? 'px' : '') } export { fuzzyEquals, distance, getRadiusFromAngleChord, ccw, ccwRaw, angleFromThreePoints, pointAlongArc, getVectorAngle, rayIntersection, lineIntersection, parsePixelStyle, isOutwardVector, normalizeVector, rotateVector, negateVector, addVectors, otpModeToGtfsType, renderDataToSvgPath, sm, getFontSizeWithUnit }