/// <summary>
        /// Gets the next point.
        /// </summary>
        /// <param name="points">The points.</param>
        /// <param name="currentPoint">The current point.</param>
        /// <returns></returns>
        internal static LRSPoint GetNextPoint(ref List <LRSPoint> points, LRSPoint currentPoint)
        {
            var index = points.IndexOf(currentPoint);

            // return null if index is -1 or last index
            return(index >= 0 && index < points.Count - 1 ? points[index + 1] : null);
        }
        /// <summary>
        /// Gets the previous point.
        /// </summary>
        /// <param name="points">The points.</param>
        /// <param name="currentPoint">The current point.</param>
        /// <returns></returns>
        internal static LRSPoint GetPreviousPoint(ref List <LRSPoint> points, LRSPoint currentPoint)
        {
            var index = points.IndexOf(currentPoint);

            // return null if index is -1; null for first point
            return(index > 0 ? points[index - 1] : null);
        }
 /// <summary>
 /// Sets the offset bearing.
 /// </summary>
 /// <param name="nextPoint">The next point.</param>
 internal void SetOffsetBearing(LRSPoint nextPoint)
 {
     if (nextPoint != null)
     {
         OffsetBearing = CalculateOffsetBearing(nextPoint);
     }
 }
        /// <summary>
        /// Calculates the offset angle.
        /// </summary>
        /// <param name="previousPoint">The current point.</param>
        /// <param name="isNegativeOffset">Is Offset is Negative</param>
        private double CalculateOffsetAngle(LRSPoint previousPoint, bool isNegativeOffset)
        {
            double offsetAngle = 0;

            var previousPointOffsetBearing = previousPoint?.OffsetBearing;

            // Left
            if (!isNegativeOffset)
            {
                if (OffsetBearing == null)
                {
                    if (previousPointOffsetBearing != null)
                    {
                        offsetAngle = (double)previousPointOffsetBearing - 90;
                    }
                }
                else if (previousPointOffsetBearing == null)
                {
                    offsetAngle = (double)OffsetBearing - 90;
                }
                else
                {
                    //(360 + b1.OffsetBearing - ((360 - ((b2.OffsetBearing + 180) - b1.OffsetBearing)) / 2)) % 360
                    offsetAngle = (
                        360 + (double)OffsetBearing -
                        (
                            (
                                360 - (
                                    ((double)previousPointOffsetBearing + 180) - (double)OffsetBearing
                                    )
                            ) / 2
                        )
                        ) % 360;
                }
            }
            // Right
            else
            {
                if (OffsetBearing == null)
                {
                    if (previousPointOffsetBearing != null)
                    {
                        offsetAngle = (double)previousPointOffsetBearing + 90;
                    }
                }
                else if (previousPointOffsetBearing == null)
                {
                    offsetAngle = (double)OffsetBearing + 90;
                }
                else
                {
                    // (b1.OffsetBearing + ((((b2.OffsetBearing + 180) - b1.OffsetBearing)) / 2)) % 360
                    offsetAngle = ((double)OffsetBearing + (((((double)previousPointOffsetBearing + 180) - (double)OffsetBearing)) / 2)) % 360;
                }
            }

            return(offsetAngle);
        }
        /// <summary>
        /// Gets the second point radian.
        /// </summary>
        /// <param name="nextPoint">The next point.</param>
        /// <param name="middlePoint">The middle point.</param>
        /// <returns></returns>
        private double GetSecondPointRadian(LRSPoint nextPoint, LRSPoint middlePoint)
        {
            var atan = GetAtanInDegree(middlePoint, nextPoint);

            atan = 90 - atan;
            atan = atan <= 0 ? 360 + atan : atan;

            return(SpatialUtil.ToRadians(180 - atan));
        }
        /// <summary>
        /// Gets the first point radian.
        /// </summary>
        /// <param name="previousPoint">The previous point.</param>
        /// <param name="middlePoint">The middle point.</param>
        /// <returns></returns>
        private double GetFirstPointRadian(LRSPoint previousPoint, LRSPoint middlePoint)
        {
            var atan = GetAtanInDegree(middlePoint, previousPoint);

            atan = 90 - atan;
            atan = atan <= 0 ? 360 + atan : atan;

            return(SpatialUtil.ToRadians(360 - atan));
        }
        /// <summary>
        /// Updates the length.
        /// </summary>
        /// <param name="currentPoint">The current point.</param>
        private void UpdateLength(LRSPoint currentPoint)
        {
            if (!_points.Any() || _points.Count <= 0)
            {
                return;
            }
            var previousPoint = _points.Last();

            Length += previousPoint.GetDistance(currentPoint);
        }
        /// <summary>
        /// Locates the point.
        /// </summary>
        /// <param name="measure">The measure.</param>
        /// <param name="firstPoint"></param>
        /// <returns></returns>
        internal LRSPoint LocatePoint(double measure, LRSPoint firstPoint = null)
        {
            var startPoint = firstPoint ?? GetStartPoint();
            var endPoint   = GetEndPoint();

            if (startPoint.M == null || endPoint.M == null)
            {
                return(null);
            }

            var fraction = (measure - startPoint.M.Value) / (endPoint.M.Value - startPoint.M.Value);
            var newX     = (startPoint.X * (1 - fraction)) + (endPoint.X * fraction);
            var newY     = (startPoint.Y * (1 - fraction)) + (endPoint.Y * fraction);

            return(new LRSPoint(newX, newY, null, measure, SRID));
        }
        /// <summary>
        /// Calculates the slope.
        /// </summary>
        /// <param name="nextLRSPoint">The next LRS point.</param>
        /// <param name="slopeValue"></param>
        internal double?GetSlope(LRSPoint nextLRSPoint, out SlopeValue slopeValue)
        {
            slopeValue = SlopeValue.None;
            var xDifference = nextLRSPoint.X - X;
            var yDifference = nextLRSPoint.Y - Y;

            if (xDifference.EqualsTo(0))
            {
                slopeValue = yDifference > 0 ? SlopeValue.PositiveInfinity : SlopeValue.NegativeInfinity;
                return(null);
            }
            else if (yDifference.EqualsTo(0))
            {
                slopeValue = xDifference > 0 ? SlopeValue.PositiveZero : SlopeValue.NegativeZero;
                return(null);
            }

            return(yDifference / xDifference);
        }
        /// <summary>
        /// Gets the deviation angle of 3 points.
        /// </summary>
        /// <param name="pointA">The point a.</param>
        /// <param name="pointO">The point o.</param>
        /// <param name="pointB">The point b.</param>
        /// <param name="isNegativeOffset">if set to <c>true</c> [is negative offset].</param>
        /// <returns></returns>
        private double GetAOBAngle(LRSPoint pointA, LRSPoint pointO, LRSPoint pointB, bool isNegativeOffset)
        {
            const double angleCorrection = Math.PI / 2;
            const double angleConversion = 2 * Math.PI;

            var atanAo = pointA.GetAtanInRadian(pointO) + angleCorrection;
            var atanBo = pointB.GetAtanInRadian(pointO) + angleCorrection;

            // angle conversion
            atanAo = SpatialUtil.ToDegrees(atanAo <= 0 ? angleConversion + atanAo : atanAo);
            atanBo = SpatialUtil.ToDegrees(atanBo <= 0 ? angleConversion + atanBo : atanBo);

            var deviationAngle =
                360 - (atanAo > atanBo
                    ? 360 - (atanAo - atanBo)
                    : atanBo - atanAo);

            // for positive offset; offset curve will be to the left of input geom;
            // so for positive deviation angle the computed angle should be subtracted from 360
            // for negative offset; offset curve will be to the right of input geom
            return(isNegativeOffset ? deviationAngle : 360 - deviationAngle);
        }
 /// <summary>
 /// Gets the distance from point.
 /// </summary>
 /// <param name="nextPoint">The next point.</param>
 /// <returns>Offset Point.</returns>
 internal double GetDistance(LRSPoint nextPoint)
 {
     return(SpatialExtensions.GetDistance(X, Y, nextPoint.X, nextPoint.Y));
 }
 /// <summary>
 /// Calculates the slope.
 /// </summary>
 /// <param name="nextLRSPoint">The next LRS point.</param>
 internal void CalculateSlope(LRSPoint nextLRSPoint)
 {
     Slope = GetSlope(nextLRSPoint, out SlopeType);
 }
        /// <summary>
        /// Compute and populate parallel points on bend lines.
        /// </summary>
        /// <param name="offset">The offset.</param>
        /// <param name="points">The points.</param>
        /// <param name="tolerance"></param>
        /// <returns>Point parallel to the current point.</returns>
        internal List <LRSPoint> GetAndPopulateParallelPoints(double offset, double tolerance, ref List <LRSPoint> points)
        {
            // list to capture additional vertices.
            var lrsPoints = new List <LRSPoint>();

            // parallel point to the current point
            var parallelPoint = GetParallelPoint();

            // get previous and next point
            var previousPoint = GetPreviousPoint(ref points, this);
            var nextPoint     = GetNextPoint(ref points, this);

            // offset distance between parallel point and input point
            var diffInDistance = Math.Round(parallelPoint.GetDistance(this), 5);
            // offset distance difference between parallel point and input point
            var offsetDiff = Math.Abs(Math.Abs(diffInDistance) - Math.Abs(offset));

            if (offsetDiff <= tolerance || previousPoint == null || nextPoint == null)
            {
                lrsPoints.Add(parallelPoint);
            }
            else
            {
                var negativeOffset = offset < 0;
                var deviationAngle = GetAOBAngle(previousPoint, this, nextPoint, negativeOffset);

                if (deviationAngle <= 90)
                {
                    var firsPointRadian = GetFirstPointRadian(previousPoint, this);
                    var nextPointRadian = GetSecondPointRadian(nextPoint, this);

                    // first point
                    var firstPointX = X + (offset * Math.Cos(firsPointRadian));
                    var firstPointY = Y + (offset * Math.Sin(firsPointRadian));
                    var firstPoint  = new LRSPoint(firstPointX, firstPointY, null, M, _srid);

                    // second point
                    var secondPointX = X + (offset * Math.Cos(nextPointRadian));
                    var secondPointY = Y + (offset * Math.Sin(nextPointRadian));
                    var secondPoint  = new LRSPoint(secondPointX, secondPointY, null, M, _srid);

                    // if computed first point is within tolerance of second point then add only first point
                    if (firstPoint.GetDistance(secondPoint) <= tolerance)
                    {
                        lrsPoints.Add(firstPoint);
                    }
                    else
                    {
                        // add first point
                        lrsPoints.Add(firstPoint);

                        // compute middle point
                        var fraction    = Math.Abs(offset / OffsetDistance);
                        var middleX     = (X * (1 - fraction)) + (parallelPoint.X * fraction);
                        var middleY     = (Y * (1 - fraction)) + (parallelPoint.Y * fraction);
                        var middlePoint = new LRSPoint(middleX, middleY, null, M, _srid);

                        // if not within tolerance add middle point
                        if (firstPoint.GetDistance(middlePoint) > tolerance)
                        {
                            lrsPoints.Add(middlePoint);
                        }

                        // add second point
                        lrsPoints.Add(secondPoint);
                    }
                }
                else
                {
                    lrsPoints.Add(parallelPoint);
                }
            }

            return(lrsPoints);
        }
 /// <summary>
 /// Gets the offset point.
 /// </summary>
 /// <param name="nextPoint">The next point.</param>
 /// <returns>Offset Point.</returns>
 private LRSPoint GetOffsetPoint(LRSPoint nextPoint)
 {
     return(this - nextPoint);
 }
 /// <summary>
 /// Sets the offset angle.
 /// </summary>
 /// <param name="previousPoint">The current point.</param>
 /// <param name="isNegativeOffset">Is Offset is Negative</param>
 internal void SetOffsetAngle(LRSPoint previousPoint, bool isNegativeOffset)
 {
     _offsetAngle = CalculateOffsetAngle(previousPoint, isNegativeOffset);
 }
 /// <summary>
 /// Calculates the offset bearing.
 /// </summary>
 /// <param name="nextPoint">The next point.</param>
 private double CalculateOffsetBearing(LRSPoint nextPoint)
 {
     _angle = SpatialUtil.ToDegrees(GetAtanInRadian(nextPoint));
     return((90 - _angle + 360) % 360);
 }
 /// <summary>
 /// Re calculate the measure.
 /// </summary>
 /// <param name="previousPoint">The previous point.</param>
 /// <param name="currentLength"></param>
 /// <param name="totalLength">The total length.</param>
 /// <param name="startMeasure">The start measure.</param>
 /// <param name="endMeasure">The end measure.</param>
 internal void ReCalculateMeasure(LRSPoint previousPoint, ref double currentLength, double totalLength, double startMeasure, double endMeasure)
 {
     currentLength += GetDistance(previousPoint);
     M              = startMeasure + (currentLength / totalLength) * (endMeasure - startMeasure);
 }
        /// <summary>
        /// Gets the arc to tangent.
        /// </summary>
        /// <param name="nextPoint">The next point.</param>
        /// <returns>In Radian</returns>
        private double GetAtanInRadian(LRSPoint nextPoint)
        {
            var offsetPoint = GetOffsetPoint(nextPoint);

            return(Math.Atan2(offsetPoint.Y, offsetPoint.X));
        }
        /// <summary>
        /// Gets the atan2 in degrees.
        /// This does angle correct when atan2 value is negative
        /// </summary>
        /// <param name="point1">The point1.</param>
        /// <param name="point2">The point2.</param>
        /// <returns>Atan2 in degrees</returns>
        private double GetAtanInDegree(LRSPoint point1, LRSPoint point2)
        {
            var atan = point1.GetAtanInRadian(point2);

            return(SpatialUtil.ToDegrees(atan <= 0 ? (2 * Math.PI) + atan : atan));
        }
 /// <summary>
 /// Adds the point.
 /// </summary>
 /// <param name="lrsPoint">The l rs point.</param>
 internal void AddPoint(LRSPoint lrsPoint)
 {
     UpdateLength(lrsPoint);
     _points.Add(lrsPoint);
 }
 /// <summary>
 /// Determines whether X, Y co-ordinates of current and second point is within tolerance
 /// </summary>
 /// <param name="secondPoint">The second point.</param>
 /// <param name="tolerance">The tolerance.</param>
 /// <returns>
 ///   <c>true</c> if X, Y co-ordinates of current and second point is within tolerance; otherwise, <c>false</c>.
 /// </returns>
 internal bool IsXYWithinTolerance(LRSPoint secondPoint, double tolerance)
 {
     return(Math.Abs(X - secondPoint.X) <= tolerance && Math.Abs(Y - secondPoint.Y) <= tolerance);
 }