private static void ComputeSegmentsInner(List <Segment> segments, Reach reach)
        {
            if (reach.HasDam)
            {
                foreach (var node in reach.Nodes)
                {
                    if (node.Dam == null)
                    {
                        continue;
                    }
                    var segment = new Segment {
                        Dam = node.Dam
                    };
                    segment.UpstreamLength = ComputeSegmentLength(reach, node, segment.Id);
                    segments.Add(segment);
                }
            }

            if (reach.UpstreamReaches != null)
            {
                foreach (var up in reach.UpstreamReaches)
                {
                    ComputeSegmentsInner(segments, up);
                }
            }
        }
 private static void AddSegmentIdToReach(Reach reach, int id)
 {
     if (reach.SegmentId == null)
     {
         reach.SegmentId = new List <int>();
     }
     if (!reach.SegmentId.Contains(id))
     {
         reach.SegmentId.Add(id);
     }
 }
        private static void InitializeNetworkInner(List <Reach> reaches, Reach root)
        {
            // get the root reach
            // using the root reach, find the end node (first or last in list)
            // search the nodes for matches, get list of reaches that represent children
            // add list of reaches to root reach
            _inChecklist.Add(root.Id);
            foreach (var r in reaches)
            {
                if (_inChecklist.Contains(r.Id))
                {
                    continue;
                }

                if (root.Upstream.Location.Match(r.Downstream.Location, 5) ||
                    root.Upstream.Location.Match(r.Upstream.Location, 5))
                {
                    if (root.Upstream.Location.Match(r.Upstream.Location, 5))
                    {
                        r.Nodes.Reverse();
                    }
                    //Debug.Assert(root.Upstream.Location.Match(r.Downstream.Location));

                    if (root.UpstreamReaches == null)
                    {
                        root.UpstreamReaches = new List <Reach>();
                    }

                    if (r.Id == root.DownstreamReach?.Id)
                    {
                        throw new ArgumentException("cycle in graph");
                    }

                    _inChecklist.Add(r.Id);
                    root.UpstreamReaches.Add(r);

                    if (r.DownstreamReach != null)
                    {
                        throw new ArgumentException("initialization error");
                    }

                    r.DownstreamReach = root;
                }
                Debug.Assert(!root.Downstream.Location.Match(r.Upstream.Location));
                Debug.Assert(!root.Downstream.Location.Match(r.Downstream.Location));
            }
            if (root.UpstreamReaches != null)
            {
                foreach (var u in root.UpstreamReaches)
                {
                    InitializeNetworkInner(reaches, u);
                }
            }
        }
        private static List <Segment> ComputeSegments(Reach outlet)
        {
            var segments = new List <Segment>
            {
                new Segment
                {
                    UpstreamLength = ComputeSegmentLength(outlet, outlet.Downstream, 0)
                }
            };

            ComputeSegmentsInner(segments, outlet);
            return(segments);
        }
        private static double ComputeSegmentLengthInner(Reach reach, int id, double?fromDam = null)
        {
            _debugLevel++;
            var distance = 0.0;

            if (reach.UpstreamReaches != null)
            {
                foreach (var ur in reach.UpstreamReaches)
                {
                    AddSegmentIdToReach(ur, id);

                    Print($"{ur}");

                    if (ur.HasDam)
                    {
                        var toDam = 0.0;
                        for (var i = 0; i < ur.Nodes.Count - 1; i++)
                        {
                            if (ur.Nodes[i].Dam != null)
                            {
                                break;
                            }
                            toDam += ur.Nodes[i].Location.Distance(ur.Nodes[i + 1].Location);
                            ur.Nodes[i].SegmentId = id;
                        }

                        Print($" TO DAM: {toDam}");
                        distance += toDam;
                    }
                    else
                    {
                        distance += ComputeSegmentLengthInner(ur, id);
                    }
                }
            }
            _debugLevel--;

            distance += fromDam ?? reach.Length;
            Print($"{reach.Id}: {distance}" + (fromDam != null ? $" FD:{fromDam}" : ""));

            return(distance);
        }
        /// <summary>
        /// Compute the length of river in the given segment and mark each reach with the segment
        /// id for later creation of the segment network.
        /// </summary>
        private static double ComputeSegmentLength(Reach reach, Node node, int id)
        {
            AddSegmentIdToReach(reach, id);
            var index    = reach.Nodes.IndexOf(node);
            var distance = 0.0;

            for (var i = index; i < reach.Nodes.Count - 1; i++)
            {
                if (i != index && reach.Nodes[i].Dam != null) // dam in the same reach
                {
                    return(distance);
                }
                distance += reach.Nodes[i].Location.Distance(reach.Nodes[i + 1].Location);
                reach.Nodes[i].SegmentId = id;
            }

            Print($"SEGMENT: {id} - {reach}");
            var rv = ComputeSegmentLengthInner(reach, id, distance);

            Print($"SEGMENT: {id} is {rv}");
            return(rv);
        }
        /// <summary>
        /// Parses the so-called "well known text" LINESTRING into a list of nodes.
        /// </summary>
        /// <param name="wkt">The well known text string</param>
        /// <param name="outlet">Location of the outlet</param>
        /// <param name="arcId">Id of the arc or "reach"</param>
        /// <returns></returns>
        private static Reach ParseLinestring(String wkt, string arcId, Location outlet)
        {
            var reach = new Reach();
            var nodes = new List <Node>();

            reach.Nodes = nodes;
            reach.Id    = int.Parse(arcId);

            // todo: this is super-simplistic with no error checking/recovery.

            // LINESTRING ( x y, x y, ... )
            var s1 = wkt.Replace("LINESTRING (", "");
            var s2 = s1.Replace(")", "");
            var s3 = s2.Split(',');

            var isOutlet = false;

            foreach (var s in s3)
            {
                var node = new Node();
                var pt   = s.Split(' ');
                node.Location.Longitude = Double.Parse(pt[0]);
                node.Location.Latitude  = Double.Parse(pt[1]);

                if (node.Location.Match(outlet))
                {
                    isOutlet = true;
                }
                nodes.Add(node);
            }

            if (isOutlet && outlet.Match(reach.Upstream.Location))
            {
                reach.Nodes.Reverse();
                Debug.Assert(reach.Downstream.Location.Match(outlet));
            }
            reach.Outlet = isOutlet;
            return(reach);
        }