UNPKG

node-red-contrib-tak-registration

Version:

A Node-RED node to register to TAK and to help wrap files as datapackages to send to TAK

290 lines (288 loc) 12.3 kB
import BufferParameters from './BufferParameters' import NotRepresentableException from '../../algorithm/NotRepresentableException' import CGAlgorithms from '../../algorithm/CGAlgorithms' import Position from '../../geomgraph/Position' import Coordinate from '../../geom/Coordinate' import OffsetSegmentString from './OffsetSegmentString' import LineSegment from '../../geom/LineSegment' import Angle from '../../algorithm/Angle' import RobustLineIntersector from '../../algorithm/RobustLineIntersector' import HCoordinate from '../../algorithm/HCoordinate' export default class OffsetSegmentGenerator { constructor () { this._maxCurveSegmentError = 0.0 this._filletAngleQuantum = null this._closingSegLengthFactor = 1 this._segList = null this._distance = 0.0 this._precisionModel = null this._bufParams = null this._li = null this._s0 = null this._s1 = null this._s2 = null this._seg0 = new LineSegment() this._seg1 = new LineSegment() this._offset0 = new LineSegment() this._offset1 = new LineSegment() this._side = 0 this._hasNarrowConcaveAngle = false const precisionModel = arguments[0] const bufParams = arguments[1] const distance = arguments[2] this._precisionModel = precisionModel this._bufParams = bufParams this._li = new RobustLineIntersector() this._filletAngleQuantum = Math.PI / 2.0 / bufParams.getQuadrantSegments() if (bufParams.getQuadrantSegments() >= 8 && bufParams.getJoinStyle() === BufferParameters.JOIN_ROUND) this._closingSegLengthFactor = OffsetSegmentGenerator.MAX_CLOSING_SEG_LEN_FACTOR this.init(distance) } addNextSegment (p, addStartPoint) { this._s0 = this._s1 this._s1 = this._s2 this._s2 = p this._seg0.setCoordinates(this._s0, this._s1) this.computeOffsetSegment(this._seg0, this._side, this._distance, this._offset0) this._seg1.setCoordinates(this._s1, this._s2) this.computeOffsetSegment(this._seg1, this._side, this._distance, this._offset1) if (this._s1.equals(this._s2)) return null const orientation = CGAlgorithms.computeOrientation(this._s0, this._s1, this._s2) const outsideTurn = (orientation === CGAlgorithms.CLOCKWISE && this._side === Position.LEFT) || (orientation === CGAlgorithms.COUNTERCLOCKWISE && this._side === Position.RIGHT) if (orientation === 0) { this.addCollinear(addStartPoint) } else if (outsideTurn) { this.addOutsideTurn(orientation, addStartPoint) } else { this.addInsideTurn(orientation, addStartPoint) } } addLineEndCap (p0, p1) { const seg = new LineSegment(p0, p1) const offsetL = new LineSegment() this.computeOffsetSegment(seg, Position.LEFT, this._distance, offsetL) const offsetR = new LineSegment() this.computeOffsetSegment(seg, Position.RIGHT, this._distance, offsetR) const dx = p1.x - p0.x const dy = p1.y - p0.y const angle = Math.atan2(dy, dx) switch (this._bufParams.getEndCapStyle()) { case BufferParameters.CAP_ROUND: this._segList.addPt(offsetL.p1) this.addFilletArc(p1, angle + Math.PI / 2, angle - Math.PI / 2, CGAlgorithms.CLOCKWISE, this._distance) this._segList.addPt(offsetR.p1) break case BufferParameters.CAP_FLAT: this._segList.addPt(offsetL.p1) this._segList.addPt(offsetR.p1) break case BufferParameters.CAP_SQUARE: const squareCapSideOffset = new Coordinate() squareCapSideOffset.x = Math.abs(this._distance) * Math.cos(angle) squareCapSideOffset.y = Math.abs(this._distance) * Math.sin(angle) const squareCapLOffset = new Coordinate(offsetL.p1.x + squareCapSideOffset.x, offsetL.p1.y + squareCapSideOffset.y) const squareCapROffset = new Coordinate(offsetR.p1.x + squareCapSideOffset.x, offsetR.p1.y + squareCapSideOffset.y) this._segList.addPt(squareCapLOffset) this._segList.addPt(squareCapROffset) break default: } } getCoordinates () { const pts = this._segList.getCoordinates() return pts } addMitreJoin (p, offset0, offset1, distance) { let isMitreWithinLimit = true let intPt = null try { intPt = HCoordinate.intersection(offset0.p0, offset0.p1, offset1.p0, offset1.p1) const mitreRatio = distance <= 0.0 ? 1.0 : intPt.distance(p) / Math.abs(distance) if (mitreRatio > this._bufParams.getMitreLimit()) isMitreWithinLimit = false } catch (ex) { if (ex instanceof NotRepresentableException) { intPt = new Coordinate(0, 0) isMitreWithinLimit = false } else throw ex } finally {} if (isMitreWithinLimit) { this._segList.addPt(intPt) } else { this.addLimitedMitreJoin(offset0, offset1, distance, this._bufParams.getMitreLimit()) } } addFilletCorner (p, p0, p1, direction, radius) { const dx0 = p0.x - p.x const dy0 = p0.y - p.y let startAngle = Math.atan2(dy0, dx0) const dx1 = p1.x - p.x const dy1 = p1.y - p.y const endAngle = Math.atan2(dy1, dx1) if (direction === CGAlgorithms.CLOCKWISE) { if (startAngle <= endAngle) startAngle += 2.0 * Math.PI } else { if (startAngle >= endAngle) startAngle -= 2.0 * Math.PI } this._segList.addPt(p0) this.addFilletArc(p, startAngle, endAngle, direction, radius) this._segList.addPt(p1) } addOutsideTurn (orientation, addStartPoint) { if (this._offset0.p1.distance(this._offset1.p0) < this._distance * OffsetSegmentGenerator.OFFSET_SEGMENT_SEPARATION_FACTOR) { this._segList.addPt(this._offset0.p1) return null } if (this._bufParams.getJoinStyle() === BufferParameters.JOIN_MITRE) { this.addMitreJoin(this._s1, this._offset0, this._offset1, this._distance) } else if (this._bufParams.getJoinStyle() === BufferParameters.JOIN_BEVEL) { this.addBevelJoin(this._offset0, this._offset1) } else { if (addStartPoint) this._segList.addPt(this._offset0.p1) this.addFilletCorner(this._s1, this._offset0.p1, this._offset1.p0, orientation, this._distance) this._segList.addPt(this._offset1.p0) } } createSquare (p) { this._segList.addPt(new Coordinate(p.x + this._distance, p.y + this._distance)) this._segList.addPt(new Coordinate(p.x + this._distance, p.y - this._distance)) this._segList.addPt(new Coordinate(p.x - this._distance, p.y - this._distance)) this._segList.addPt(new Coordinate(p.x - this._distance, p.y + this._distance)) this._segList.closeRing() } addSegments (pt, isForward) { this._segList.addPts(pt, isForward) } addFirstSegment () { this._segList.addPt(this._offset1.p0) } addLastSegment () { this._segList.addPt(this._offset1.p1) } initSideSegments (s1, s2, side) { this._s1 = s1 this._s2 = s2 this._side = side this._seg1.setCoordinates(s1, s2) this.computeOffsetSegment(this._seg1, side, this._distance, this._offset1) } addLimitedMitreJoin (offset0, offset1, distance, mitreLimit) { const basePt = this._seg0.p1 const ang0 = Angle.angle(basePt, this._seg0.p0) // const ang1 = Angle.angle(basePt, this._seg1.p1) const angDiff = Angle.angleBetweenOriented(this._seg0.p0, basePt, this._seg1.p1) const angDiffHalf = angDiff / 2 const midAng = Angle.normalize(ang0 + angDiffHalf) const mitreMidAng = Angle.normalize(midAng + Math.PI) const mitreDist = mitreLimit * distance const bevelDelta = mitreDist * Math.abs(Math.sin(angDiffHalf)) const bevelHalfLen = distance - bevelDelta const bevelMidX = basePt.x + mitreDist * Math.cos(mitreMidAng) const bevelMidY = basePt.y + mitreDist * Math.sin(mitreMidAng) const bevelMidPt = new Coordinate(bevelMidX, bevelMidY) const mitreMidLine = new LineSegment(basePt, bevelMidPt) const bevelEndLeft = mitreMidLine.pointAlongOffset(1.0, bevelHalfLen) const bevelEndRight = mitreMidLine.pointAlongOffset(1.0, -bevelHalfLen) if (this._side === Position.LEFT) { this._segList.addPt(bevelEndLeft) this._segList.addPt(bevelEndRight) } else { this._segList.addPt(bevelEndRight) this._segList.addPt(bevelEndLeft) } } computeOffsetSegment (seg, side, distance, offset) { const sideSign = side === Position.LEFT ? 1 : -1 const dx = seg.p1.x - seg.p0.x const dy = seg.p1.y - seg.p0.y const len = Math.sqrt(dx * dx + dy * dy) const ux = sideSign * distance * dx / len const uy = sideSign * distance * dy / len offset.p0.x = seg.p0.x - uy offset.p0.y = seg.p0.y + ux offset.p1.x = seg.p1.x - uy offset.p1.y = seg.p1.y + ux } addFilletArc (p, startAngle, endAngle, direction, radius) { const directionFactor = direction === CGAlgorithms.CLOCKWISE ? -1 : 1 const totalAngle = Math.abs(startAngle - endAngle) const nSegs = Math.trunc(totalAngle / this._filletAngleQuantum + 0.5) if (nSegs < 1) return null const initAngle = 0.0 const currAngleInc = totalAngle / nSegs let currAngle = initAngle const pt = new Coordinate() while (currAngle < totalAngle) { const angle = startAngle + directionFactor * currAngle pt.x = p.x + radius * Math.cos(angle) pt.y = p.y + radius * Math.sin(angle) this._segList.addPt(pt) currAngle += currAngleInc } } addInsideTurn (orientation, addStartPoint) { this._li.computeIntersection(this._offset0.p0, this._offset0.p1, this._offset1.p0, this._offset1.p1) if (this._li.hasIntersection()) { this._segList.addPt(this._li.getIntersection(0)) } else { this._hasNarrowConcaveAngle = true if (this._offset0.p1.distance(this._offset1.p0) < this._distance * OffsetSegmentGenerator.INSIDE_TURN_VERTEX_SNAP_DISTANCE_FACTOR) { this._segList.addPt(this._offset0.p1) } else { this._segList.addPt(this._offset0.p1) if (this._closingSegLengthFactor > 0) { const mid0 = new Coordinate((this._closingSegLengthFactor * this._offset0.p1.x + this._s1.x) / (this._closingSegLengthFactor + 1), (this._closingSegLengthFactor * this._offset0.p1.y + this._s1.y) / (this._closingSegLengthFactor + 1)) this._segList.addPt(mid0) const mid1 = new Coordinate((this._closingSegLengthFactor * this._offset1.p0.x + this._s1.x) / (this._closingSegLengthFactor + 1), (this._closingSegLengthFactor * this._offset1.p0.y + this._s1.y) / (this._closingSegLengthFactor + 1)) this._segList.addPt(mid1) } else { this._segList.addPt(this._s1) } this._segList.addPt(this._offset1.p0) } } } createCircle (p) { const pt = new Coordinate(p.x + this._distance, p.y) this._segList.addPt(pt) this.addFilletArc(p, 0.0, 2.0 * Math.PI, -1, this._distance) this._segList.closeRing() } addBevelJoin (offset0, offset1) { this._segList.addPt(offset0.p1) this._segList.addPt(offset1.p0) } init (distance) { this._distance = distance this._maxCurveSegmentError = distance * (1 - Math.cos(this._filletAngleQuantum / 2.0)) this._segList = new OffsetSegmentString() this._segList.setPrecisionModel(this._precisionModel) this._segList.setMinimumVertexDistance(distance * OffsetSegmentGenerator.CURVE_VERTEX_SNAP_DISTANCE_FACTOR) } addCollinear (addStartPoint) { this._li.computeIntersection(this._s0, this._s1, this._s1, this._s2) const numInt = this._li.getIntersectionNum() if (numInt >= 2) { if (this._bufParams.getJoinStyle() === BufferParameters.JOIN_BEVEL || this._bufParams.getJoinStyle() === BufferParameters.JOIN_MITRE) { if (addStartPoint) this._segList.addPt(this._offset0.p1) this._segList.addPt(this._offset1.p0) } else { this.addFilletCorner(this._s1, this._offset0.p1, this._offset1.p0, CGAlgorithms.CLOCKWISE, this._distance) } } } closeRing () { this._segList.closeRing() } hasNarrowConcaveAngle () { return this._hasNarrowConcaveAngle } interfaces_ () { return [] } getClass () { return OffsetSegmentGenerator } static get OFFSET_SEGMENT_SEPARATION_FACTOR () { return 1.0E-3 } static get INSIDE_TURN_VERTEX_SNAP_DISTANCE_FACTOR () { return 1.0E-3 } static get CURVE_VERTEX_SNAP_DISTANCE_FACTOR () { return 1.0E-6 } static get MAX_CLOSING_SEG_LEN_FACTOR () { return 80 } }