UNPKG

@equinor/esv-intersection

Version:

Intersection component package with testing and automatic documentation.

248 lines (216 loc) 6.4 kB
import Vector2 from '@equinor/videx-vector2'; import { seqI } from '@equinor/videx-math'; import { CurveInterpolator } from 'curve-interpolator'; import { SurveySample } from './interfaces'; const stepSize = 0.1; const extensionLength = 1000; const thresholdRelativeDist = 150; const thresholdDirectionDist = 30; const pathSteps = 10; /** * Generate projected wellbore path for drawing using wellbore path layer * Code originally developed for REP * @param {[]} poslog Position log from SMDA */ export function generateProjectedWellborePath( poslog: SurveySample[], ): [number, number][] { if (!poslog || poslog.length === 0) { return []; } const points: [number, number, number, number][] = poslog ? poslog.map((p: SurveySample) => [p.easting, p.northing, p.tvd, p.md]) : []; const projection = simplify(projectCurtain(points)); const offset = projection[projection.length - 1]?.[0]; if (offset != null) { projection.forEach((p, i) => { projection[i]![0] = offset - p[0]; }); } return projection; } /** * Generate Trajectory * Code originally developed for REP * @param {[]} poslog Position log from SMDA * @param {number} defaultIntersectionAngle Default intersection angle for the field */ export function generateProjectedTrajectory( poslog: SurveySample[], defaultIntersectionAngle: number, ): number[][] { if (!poslog || poslog.length === 0) { return []; } const points: [number, number, number, number][] = poslog ? poslog.map(p => [p.easting, p.northing, p.tvd, p.md]) : []; const interpolator: CurveInterpolator = new CurveInterpolator(points, { tension: 0.75, arcDivisions: 5000, }); const displacement: number = interpolator.length; const nPoints: number = Math.round(displacement * pathSteps); let path: [number, number][]; if (nPoints > 0) { const maxOffset = 0.0005; const maxDistance = 10; path = simplify(interpolator.getPoints(nPoints), maxOffset, maxDistance); } else { path = [[points[0]![0], points[0]![1]]]; } const first = path[0]!; const last = path[path.length - 1]!; const relativeDist: number = Vector2.distance(first, last); let v: Vector2; if (relativeDist < thresholdRelativeDist) { const oneEighty = 180; const radCurtainDirection = (defaultIntersectionAngle / oneEighty) * Math.PI; v = new Vector2( Math.cos(radCurtainDirection), Math.sin(radCurtainDirection), ).mutable; } else { v = getDirectionVector(path, thresholdDirectionDist); } const extensionLengthStart: number = Math.max( 0, extensionLength - displacement, ); const offset: number = extensionLengthStart + displacement; const trajectory: [number, number][] = []; let firstPoints: [number, number][] = []; // Reference to initial vector const initial: number[] = v.toArray(); if (extensionLengthStart > 0) { // extend from start firstPoints = seqI(Math.ceil(extensionLengthStart * stepSize)).map(t => v .set(initial) .scale(extensionLengthStart * (1 - t)) .subFrom(first) .toArray(), ); firstPoints.pop(); trajectory.push(...firstPoints); } trajectory.push(...path); const endPoints = seqI(Math.ceil(extensionLength * stepSize)) .map(t => v .set(initial) .scale(extensionLength * t) .add(last) .toArray(), ) .splice(1); trajectory.push(...endPoints); const projectedTrajectory: number[][] = projectCurtain( trajectory, undefined, offset, ); return projectedTrajectory; } /** * Get direction vector * Code originally developed for REP * @param {[]} path * @param {number} threshold * @returns {Vector2} */ function getDirectionVector(path: number[][], threshold: number): Vector2 { const res: Vector2 = Vector2.zero.mutable; let len = 0; const temp: Vector2 = Vector2.zero.mutable; for (let i = 0; i < path.length - 1; i++) { const index = path.length - 1 - i; temp.set(path[index]!).sub(path[index - 1]!); res.add(temp); len = res.magnitude; if (len > threshold) { break; } } if (len === 0) { return new Vector2([0, 0]); } return res.scale(1 / len); } /** * Simplify array * * Simplifies an array using given parameters. * Code originally developed for REP * @access public * * @param {Number[]} inputArr Array to simplify * @param {Number} [maxOffset=0.001] Max offset (Default = 0.001) * @param {Number} [maxDistance=10] Max distance (Default = 10) * * @return {Number[]} Simplified array */ function simplify( inputArr: [number, number][], maxOffset = 0.001, maxDistance = 10, ): [number, number][] { if (inputArr.length <= 4) { return inputArr; } const [o0, o1] = inputArr[0]!; const arr = inputArr.map<[number, number]>(d => [d[0]! - o0, d[1]! - o1]); let [a0, a1] = arr[0]!; const sim = [inputArr[0]!]; for (let i = 1; i + 1 < arr.length; i++) { const [t0, t1] = arr[i] ?? []; const [b0, b1] = arr[i + 1] ?? []; // If t->b vector is NOT [0, 0] if ( t0 != null && t1 != null && b0 != null && b1 != null && (b0 - t0 !== 0 || b1 - t1 !== 0) ) { // Proximity check const proximity: number = Math.abs(a0 * b1 - a1 * b0 + b0 * t1 - b1 * t0 + a1 * t0 - a0 * t1) / Math.sqrt((b0 - a0) ** 2 + (b1 - a1) ** 2); const dir: [number, number] = [a0 - t0, a1 - t1]; const len: number = Math.sqrt(dir[0] ** 2 + dir[1] ** 2); if (proximity > maxOffset || len >= maxDistance) { sim.push([t0 + o0, t1 + o1]); [a0, a1] = [t0, t1]; } } } const last = arr[arr.length - 1]!; sim.push([last[0] + o0, last[1] + o1]); return sim; } /** * Perform a curtain projection on a set of points in 3D * @param points * @param origin * @param offset * @returns {array} */ function projectCurtain( points: [number, number, ...number[]][], origin?: [number, number, number, number], offset = 0, ): [number, number][] { let p0 = origin || points[0]!; let l = 0; const projected = points.map<[number, number]>(p1 => { const dx = p1[0] - p0[0]; const dy = p1[1] - p0[1]; l += Math.sqrt(dx ** 2 + dy ** 2); p0 = p1; return [offset > 0 ? offset - l : l, p1[2] ?? 0]; }); return projected; }