import { Injectable } from '@angular/core'; import { CoordsInterface, } from './../../../models'; import { getItemAtOppositeSideOfArray, getItemsToEitherSideOfArrayIndex, getMid, getNumberWithinConstraints, } from './../../functions/index'; @Injectable() export class CoordinateUtilitiesService { public getNearestDegrees( degrees, getNearest = 90, ) { return Math.round(degrees / getNearest) * getNearest; } public rotateCoords( pointsToRotate: CoordsInterface, centerCoords: CoordsInterface, angle: number, ): CoordsInterface { const radians = (Math.PI / 180) * angle; const cos = Math.cos(radians); const sin = Math.sin(radians); const x = (cos * (pointsToRotate.x - centerCoords.x)) + (sin * (pointsToRotate.y - centerCoords.y)) + centerCoords.x; const y = (cos * (pointsToRotate.y - centerCoords.y)) - (sin * (pointsToRotate.x - centerCoords.x)) + centerCoords.y; return { x, y, }; } public rotateCoordsAroundCenterOfCoords( pointsToRotate: CoordsInterface[], angle: number, ) { const xCoords = pointsToRotate.map(({ x }) => x); const yCoords = pointsToRotate.map(({ y }) => y); const centerCoords = { x: getMid(xCoords), y: getMid(yCoords), }; return pointsToRotate.map((coords) => ( this.rotateCoords( coords, centerCoords, angle, ) )); } public doCoordsFillArea( areaToFill: CoordsInterface[], elementToCheckMinCoords: CoordsInterface, elementToCheckMaxCoords: CoordsInterface, ) { const xPoints = areaToFill.map((point) => point.x); const yPoints = areaToFill.map((point) => point.y); const minGuideAreaX = Math.min(...xPoints); const maxGuideAreaX = Math.max(...xPoints); const minGuideAreaY = Math.min(...yPoints); const maxGuideAreaY = Math.max(...yPoints); const tooFarRight = Math.round(elementToCheckMinCoords.x) > Math.round(minGuideAreaX); const tooFarLeft = Math.round(elementToCheckMaxCoords.x) < Math.round(maxGuideAreaX); const tooLowDown = Math.round(elementToCheckMinCoords.y) > Math.round(minGuideAreaY); const tooHighUp = Math.round(elementToCheckMaxCoords.y) < Math.round(maxGuideAreaY); return ( !tooFarRight && !tooFarLeft && !tooLowDown && !tooHighUp ); } public doRotatedCoordsFillArea( areaToFill: CoordsInterface[], elementToCheckMinCoords: CoordsInterface, elementToCheckMaxCoords: CoordsInterface, elementToCheckRotation: number, pointToRotateAround: CoordsInterface, ) { const rotatedCoordsMin = this.rotateCoords( elementToCheckMinCoords, pointToRotateAround, elementToCheckRotation, ); const rotatedCoordsMax = this.rotateCoords( elementToCheckMaxCoords, pointToRotateAround, elementToCheckRotation, ); const minXAfterRotation = rotatedCoordsMin.x < rotatedCoordsMax.x ? rotatedCoordsMin.x : rotatedCoordsMax.x; const maxXAfterRotatation = rotatedCoordsMin.x > rotatedCoordsMax.x ? rotatedCoordsMin.x : rotatedCoordsMax.x; const minYAfterRotation = rotatedCoordsMin.y < rotatedCoordsMax.y ? rotatedCoordsMin.y : rotatedCoordsMax.y; const maxYAfterRotation = rotatedCoordsMin.y > rotatedCoordsMax.y ? rotatedCoordsMin.y : rotatedCoordsMax.y; return this.doCoordsFillArea( areaToFill, { x: minXAfterRotation, y: minYAfterRotation, }, { x: maxXAfterRotatation, y: maxYAfterRotation, }, ); } public getAngleBetweenCoordinates( coord1: CoordsInterface, coord2: CoordsInterface, ) { return Math.atan2(coord2.y - coord1.y, coord2.x - coord1.x) * 180 / Math.PI; } public getLineGradient( coord1: CoordsInterface, coord2: CoordsInterface, ) { return ( coord2.y - coord1.y ) / ( coord2.x - coord1.x ); } public getLineYIntercept( { x, y }: CoordsInterface, gradient: number, ) { return y - gradient * x; } public getDistanceBetweenCoords( coord1: CoordsInterface, coord2: CoordsInterface, ) { return Math.abs( Math.sqrt( Math.pow(coord2.x - coord1.x, 2) + Math.pow(coord2.y - coord1.y, 2), ), ); } public getXIntersect( yCoord: number, lineGradient: number, yAxisIntercept: number, ) { return (yCoord - yAxisIntercept) / lineGradient; } public getYIntersect( xCoord: number, lineGradient: number, yAxisIntercept: number, ) { return (lineGradient * xCoord) + yAxisIntercept; } public getCoordinateAtConstraintIntersection = ( otherCoord: CoordsInterface, currentCoord: CoordsInterface, minConstraint: CoordsInterface, maxConstraint: CoordsInterface, ) => { const gradient = this.getLineGradient( otherCoord, currentCoord, ); const intercept = this.getLineYIntercept( currentCoord, gradient, ); const { x, y } = currentCoord; const constrainedX = getNumberWithinConstraints(x, minConstraint.x, maxConstraint.x); const constrainedY = getNumberWithinConstraints(y, minConstraint.y, maxConstraint.y); const xIsOutsideConstraints = constrainedX !== x; const yIsOutsideConstraints = constrainedY !== y; return { x: xIsOutsideConstraints ? constrainedX : Math.abs(gradient) !== Infinity ? this.getXIntersect( constrainedY, gradient, intercept, ) : x, y: yIsOutsideConstraints ? constrainedY : this.getYIntersect( constrainedX, gradient, intercept, ), }; } public isCoordinatesWithinConstraints( coord: CoordsInterface, minConstraint: CoordsInterface, maxConstraint: CoordsInterface, ) { return ( coord.x === getNumberWithinConstraints( coord.x, minConstraint.x, maxConstraint.x, ) && coord.y === getNumberWithinConstraints( coord.y, minConstraint.y, maxConstraint.y, ) ); } public constrainCoordinates( coords: CoordsInterface[], minConstraint: CoordsInterface, maxConstraint: CoordsInterface, ) { const isWithinBounds = (coord: CoordsInterface) => ( this.isCoordinatesWithinConstraints( coord, minConstraint, maxConstraint, ) ); const allCoordsOutOfBounds = coords.filter(isWithinBounds).length === 0; return coords.map((currentCoord, index) => { const { x, y } = currentCoord; const constrainedX = getNumberWithinConstraints(x, minConstraint.x, maxConstraint.x); const constrainedY = getNumberWithinConstraints(y, minConstraint.y, maxConstraint.y); const xIsOutsideConstraints = constrainedX !== x; const yIsOutsideConstraints = constrainedY !== y; if (!xIsOutsideConstraints && !yIsOutsideConstraints) { return currentCoord; } if ( allCoordsOutOfBounds || ( xIsOutsideConstraints && yIsOutsideConstraints )) { return { x: constrainedX, y: constrainedY, }; } const getCoordinateAtConstraintIntersection = ( otherCoord: CoordsInterface, ) => { return this.getCoordinateAtConstraintIntersection( otherCoord, currentCoord, minConstraint, maxConstraint, ); }; const coordsWithinOneStep = getItemsToEitherSideOfArrayIndex( coords, index, ); const intersectingCoordsWithinBounds = coordsWithinOneStep .filter(isWithinBounds) .map(getCoordinateAtConstraintIntersection); if (intersectingCoordsWithinBounds.length === 0) { const oppositeCoordinate = getItemAtOppositeSideOfArray( coords, index, ); return getCoordinateAtConstraintIntersection( oppositeCoordinate, ); } const closestCoordinateAtConstraintIntersection = intersectingCoordsWithinBounds .reduce((nearestCoord, nextCoord) => { const distanceBetween = this.getDistanceBetweenCoords( currentCoord, nextCoord, ); const currentMinDistanceBetween = this.getDistanceBetweenCoords( currentCoord, nearestCoord, ); return distanceBetween < currentMinDistanceBetween ? nextCoord : nearestCoord; }, intersectingCoordsWithinBounds[0]); return closestCoordinateAtConstraintIntersection; }); } }