/// <summary>
        /// Gets vantage points from a given vantage street.
        /// Assumes that the outputVantageStreet.Centerline and the front lot line are parallel
        /// same-length offsets of each other.
        /// </summary>
        /// <param name="model">Used to help debug visualizations</param>
        /// <returns></returns>
        public static List <VantagePoint> GetVantagePoints(NYCDaylightEvaluationVantageStreet outputVantageStreet, Model model = null)
        {
            var vantagePoints         = new List <VantagePoint>();
            var ninetyDegreeDirection = (outputVantageStreet.FrontLotLine.PointAt(0.5) - outputVantageStreet.Centerline.PointAt(0.5)).Unitized();
            var hasThirdVantagePoint  = outputVantageStreet.Centerline.Length() > longCenterlineLength;

            // VP 1
            var base1   = new Vector3(outputVantageStreet.Centerline.Start);
            var dir1    = new Vector3(outputVantageStreet.Centerline.End - outputVantageStreet.Centerline.Start).Unitized();
            var origin1 = base1 + (dir1 * VantageDistance);
            var vp1     = new VantagePoint(
                outputVantageStreet,
                origin1,
                dir1 * -1,
                ninetyDegreeDirection
                );

            vantagePoints.Add(vp1);

            // VP 2
            var base2   = new Vector3(outputVantageStreet.Centerline.End);
            var dir2    = new Vector3(outputVantageStreet.Centerline.Start - outputVantageStreet.Centerline.End).Unitized();
            var origin2 = base2 + (dir2 * VantageDistance);
            var vp2     = new VantagePoint(
                outputVantageStreet,
                origin2,
                dir2 * -1,
                ninetyDegreeDirection
                );

            vantagePoints.Add(vp2);

            if (hasThirdVantagePoint)
            {
                var lineBetweenVps = new Line(origin1, origin2);
                var dir3           = dir1;
                var origin3        = lineBetweenVps.PointAt(0.5);
                var vp3            = new VantagePoint(
                    outputVantageStreet,
                    origin3,
                    dir3 * -1,
                    ninetyDegreeDirection
                    );
                vantagePoints.Add(vp3);
            }

            calculateDaylightBoundaries(outputVantageStreet, vantagePoints, model);
            foreach (var vp in vantagePoints)
            {
                vp.Diagram.CalculateProfileCurvesAndBoundingSquares();
            }

            return(vantagePoints);
        }
        public VantagePoint(
            NYCDaylightEvaluationVantageStreet vantageStreet,
            Vector3 point,
            Vector3 startDirection,
            Vector3 ninetyDegreeDirection
            )
        {
            this.VantageStreet  = vantageStreet;
            this.Point          = point;
            this.StartDirection = startDirection;
            this.FrontDirection = ninetyDegreeDirection;

            this.sPlane = new Plane(point, ninetyDegreeDirection);

            if (Math.Abs(90 - startDirection.PlaneAngleTo(ninetyDegreeDirection)) < Vector3.EPSILON)
            {
                this.dPlane = new Plane(point, startDirection);
            }
            else
            {
                this.dPlane = new Plane(point, startDirection * -1);
            }

            var lotLinesBySDist = new List <Line>(vantageStreet.LotLines).OrderBy(line => this.GetS(line.PointAt(0.5))).ToList();

            this.RearLotLine = lotLinesBySDist[3];

            var move   = -1 * this.FrontDirection * vantageStreet.CenterlineDistance;
            var points = new List <Vector3>()
            {
                this.VantageStreet.FrontLotLine.Start + move, this.VantageStreet.FrontLotLine.End + move
            };

            this.Centerline = new Polyline(points);

            var nearFarLines = new List <Line>()
            {
                lotLinesBySDist[1], lotLinesBySDist[2]
            }.OrderBy(line => Math.Abs(this.GetD(line.PointAt(0.5)))).ToList();

            this.NearLotLine = nearFarLines[0];
            this.FarLotLine  = nearFarLines[1];

            this.Diagram = new Diagram(this);
        }
        /// <summary>
        /// Create an output vantage street from an input vantage street.
        /// </summary>
        public static NYCDaylightEvaluationVantageStreet CreateVantageStreet(Polygon rectangularSite, VantageStreets vantageStreet, out List <VantagePoint> vantagePoints, VantageStreetsOverride ovd = null, Model model = null)
        {
            if (vantageStreet.Line == null)
            {
                throw new Exception("Each vantage street must have a line designating its rough location. Please draw a line outside of your lot that represents the centerline of your vantage street. It does not need to be straight or exactly parallel to the lot line, but it must exist.");
            }
            var siteCentroid         = rectangularSite.Centroid();
            var midpoint             = vantageStreet.Line.PointAt(0.5);
            var lotLines             = new List <Line>(rectangularSite.Segments()).OrderBy(segment => midpoint.DistanceTo(segment.PointAt(0.5))).ToList();
            var originalFrontLotLine = lotLines[0];
            var frontLotLine         = ovd?.Value.FrontLotLine ?? originalFrontLotLine;
            var centerlineOffsetDist = Settings.CenterlineDistances[vantageStreet.Width] / 2;
            var directionToStreet    = new Vector3(originalFrontLotLine.PointAt(0.5) - siteCentroid).Unitized() * centerlineOffsetDist;
            var centerline           = new Line(frontLotLine.Start + directionToStreet, frontLotLine.End + directionToStreet);
            var outputVantageStreet  = new NYCDaylightEvaluationVantageStreet(0, centerlineOffsetDist, frontLotLine, centerline, vantageStreet.StreetWallContinuity, lotLines, Units.FeetToMeters(vantageStreet.BlockDepthInFeet), name: vantageStreet.Name);

            if (ovd != null)
            {
                outputVantageStreet.AddOverrideIdentity(ovd);
            }
            vantagePoints = VantagePoint.GetVantagePoints(outputVantageStreet, model);

            return(outputVantageStreet);
        }
        private static void calculateDaylightBoundaries(NYCDaylightEvaluationVantageStreet vantageStreet, List <VantagePoint> orderedStreetVantagePts, Model model = null)
        {
            Console.WriteLine("Calculate Daylight Boundaries");

            if (orderedStreetVantagePts.Count < 2 || orderedStreetVantagePts.Count > 3)
            {
                throw new Exception($"Vantage streets must have a minimum of two and a maximum of three vantage points. {orderedStreetVantagePts.Count} vantage points were found.");
            }

            foreach (var vp in orderedStreetVantagePts.Take(2))
            {
                var farPoint = vp.Point + (vp.StartDirection * VantageDistance) + (vp.FrontDirection * vantageStreet.CenterlineDistance);
                vp.DaylightBoundariesPoints[0] = farPoint;
            }

            if (orderedStreetVantagePts.Count == 2)
            {
                foreach (var vp in orderedStreetVantagePts)
                {
                    var nearPointOnNearLot = new List <Vector3>()
                    {
                        vp.NearLotLine.Start, vp.NearLotLine.End
                    }.OrderBy(pt => pt.DistanceTo(vp.Point)).ToList()[0];
                    var frontageLength = vp.VantageStreet.FrontLotLine.Length();
                    if (frontageLength > VantageDistance)
                    {
                        // 81-275 Special Conditions a.1: length of street frontage > 250ft and < 500ft
                        // If > 500ft, we would've had 3 vantage points so we wouldn't have reached this point.
                        vp.DaylightBoundariesPoints[1] = nearPointOnNearLot;
                    }
                    else
                    {
                        // 81-273f:
                        // Draw a vertical line on the chart rising from the intersection of the near lot line of the zoning lot
                        // with the center line of the block or with a line 100 feet distant from and parallel to the front lot line on the vantage street,
                        // whichever line is closer to the vantage street.
                        // This line and the far lot line represent the boundaries of the potential sky area that the building could block.

                        // Move intersection point of the near lot line and front lot line
                        // towards the rear, to the lesser of 100' or the outputVantageStreet.Centerline of the block from the front lot line
                        var pointForBounds = nearPointOnNearLot + vp.FrontDirection * Math.Min(Units.FeetToMeters(100), vp.VantageStreet.BlockDepth / 2);
                        vp.DaylightBoundariesPoints[1] = pointForBounds;
                    }
                }
            }

            if (orderedStreetVantagePts.Count == 3)
            {
                foreach (var vp in orderedStreetVantagePts.SkipLast(1))
                {
                    vp.DaylightBoundariesPoints[1] = vp.Point + (vp.FrontDirection * vp.VantageStreet.CenterlineDistance);
                }
                var vp1 = orderedStreetVantagePts[0];
                var vp2 = orderedStreetVantagePts[1];
                var vp3 = orderedStreetVantagePts[2];
                vp3.DaylightBoundariesPoints[0] = vp1.DaylightBoundariesPoints[1];
                vp3.DaylightBoundariesPoints[1] = vp2.DaylightBoundariesPoints[1];
            }

            foreach (var vp in orderedStreetVantagePts)
            {
                var min = vp.GetAnalysisPoint(vp.DaylightBoundariesPoints[0]).PlanAndSection.X;
                var max = vp.GetAnalysisPoint(vp.DaylightBoundariesPoints[1]).PlanAndSection.X;

                if (max < min)
                {
                    vp.DaylightBoundariesPoints.Reverse();
                    vp.DaylightBoundaries = new Domain1d(max, min);
                }
                else
                {
                    vp.DaylightBoundaries = new Domain1d(min, max);
                }
            }
        }