Example #1
0
 public static void LoadDamCandidatesFromLayer(List <CandidateDam> candidates, BasicFeatureLayer damCandidatesLayer)
 {
     using (var cursor = damCandidatesLayer.Search())
     {
         while (cursor.MoveNext())
         {
             using (Row row = cursor.Current)
             {
                 CandidateDam candidate = new CandidateDam();
                 candidate.ObjectID             = (int)row["ObjectID"];
                 candidate.ContourID            = (int)row["ContourID"];
                 candidate.StartPointID         = (int)row["StartPointID"];
                 candidate.EndPointID           = (int)row["EndPointID"];
                 candidate.ContourHeight        = Convert.ToInt32(row["ContourHeight"]);
                 candidate.DistanceOnLine       = (int)row["DistanceOnLine"];
                 candidate.Length               = Convert.ToInt32(row["Length"]);
                 candidate.StartPointDistance   = Convert.ToInt32(row["StartPointDistance"]);
                 candidate.EndPointDistance     = Convert.ToInt32(row["EndPointDistance"]);
                 candidate.DamSpansContourStart = Convert.ToInt32(row["DamSpansContourStart"]) == 1;
                 candidate.DamHeight            = Convert.ToInt32(row["DamHeight"]);
                 candidate.Line            = (row as Feature).GetShape() as Polyline;
                 candidate.StartPoint      = candidate.Line.Points.First();
                 candidate.EndPoint        = candidate.Line.Points.Last();
                 candidate.ReservoirVolume = Convert.ToInt64(row["ReservoirVolume"]);
                 candidates.Add(candidate);
             }
         }
     }
 }
        private static CandidateDam CreateCandidateDam(int contourHeight, int minPointID, KeyValuePair <long, MapPoint> start, KeyValuePair <long, MapPoint> end, decimal Length, long distanceOnLine, bool damSpansContourStart, int contourID)
        {
            CandidateDam cand = new CandidateDam();

            cand.ContourID            = contourID;
            cand.ContourHeight        = contourHeight;
            cand.StartPoint           = start.Value;
            cand.StartPointID         = start.Key;
            cand.StartPointDistance   = (start.Key - minPointID) * pointsIntervalOnContour;
            cand.EndPointID           = end.Key;
            cand.EndPoint             = end.Value;
            cand.EndPointDistance     = (end.Key - minPointID) * pointsIntervalOnContour;
            cand.Length               = Length;
            cand.DistanceOnLine       = distanceOnLine;
            cand.DamSpansContourStart = damSpansContourStart;
            return(cand);
        }
        private static List <CandidateDam> AnalyseCountourPoints(SortedDictionary <long, MapPoint> points, int contourHeight, int contourID, CancellationToken ctoken)
        {
            int minPointID = (int)points.Min(c => c.Key);
            var first      = points.First();
            var last       = points.Last();
            //find out if the contour is a closed loop
            bool closedLoop = (first.Value.X == last.Value.X && first.Value.Y == last.Value.Y);

            //remove the last point
            points.Remove((long)last.Key);
            List <CandidateDam> candidates = new List <CandidateDam>();
            SortedDictionary <long, MapPoint> endpoints = new SortedDictionary <long, MapPoint>(points);

            //remove the first x points from the endpoints, as there is no valid candidate for a dam within the first 1000m
            for (int i = 0; i < 1000 / pointsIntervalOnContour; i++)
            {
                lock (lockingObject)
                    PotentialCandidates++;
                if (endpoints.Keys.Count == 0)
                {
                    break;
                }
                endpoints.Remove(endpoints.Keys.First());
            }
            foreach (var start in points)
            {
                lock (lockingObject)
                {
                    PointsAnalyzed++;
                    if (PointsAnalyzed % 1000 == 0)
                    {
                        cps.Progressor.Value  = Convert.ToUInt32(PointsAnalyzed * 100 / TotalPointsCount);
                        cps.Progressor.Status = string.Format("{0} Points of {1} analyzed", PointsAnalyzed.ToString("n0"), TotalPointsCount.ToString("n0"));
                    }
                }
                List <CandidateDam> pointCandidates       = new List <CandidateDam>();
                List <CandidateDam> chosenPointCandidates = new List <CandidateDam>();
                foreach (var end in endpoints)
                {
                    var DamSpansContourStart = false;
                    lock (lockingObject)
                        PotentialCandidates++;
                    //Manual Length calculation with pythagoras
                    var Length = (decimal)Math.Sqrt(Math.Pow(start.Value.X - end.Value.X, 2) + Math.Pow(start.Value.Y - end.Value.Y, 2));
                    if (Length > 1000)
                    {
                        continue;
                    }
                    var distanceOnLine = Math.Abs((start.Key - end.Key) * pointsIntervalOnContour);
                    //if the current contour is a closed loop, always take the shorter distance on either side
                    if (closedLoop && distanceOnLine > (decimal)ContourLengths[contourID] / 2.0m)
                    {
                        distanceOnLine       = Convert.ToInt64(ContourLengths[contourID]) - distanceOnLine;
                        DamSpansContourStart = true;
                    }
                    //if a candidate is detected that has less than 1.000 m reservoir circumference, skip it (only happens when DamSpansContourStart)
                    if (distanceOnLine < 1000)
                    {
                        continue;
                    }
                    //if the distanceOnLine grows bigger than 30.000 m we stop testing this startpoint, as the resulting reservoir would exceed our target magnitude
                    if (distanceOnLine > 30000 && !closedLoop)
                    {
                        break;
                    }
                    //only save candidates that have a length rating > 10
                    if (distanceOnLine / Length < 10)
                    {
                        continue;
                    }

                    CandidateDam cand = CreateCandidateDam(contourHeight, minPointID, start, end, Length, distanceOnLine, DamSpansContourStart, contourID);
                    pointCandidates.Add(cand);
                }
                //if closed loop and result was over 30.000 m then delete now
                if (closedLoop)
                {
                    foreach (var item in pointCandidates.Where(c => c.DistanceOnLine > 30000).ToList())
                    {
                        candidates.Remove(item);
                    }
                }
                if (pointCandidates.Count > 0)
                {
                    int     pointCandidateLimit = 3;
                    decimal lastRating          = 0;
                    //choose max 3 candidates per starting point and try to preserve real alternative dam variants (crossing of multiple branches)
                    foreach (var pointCandidate in pointCandidates.OrderByDescending(c => c.Rating))
                    {
                        if (pointCandidateLimit == 0)
                        {
                            break;
                        }
                        //if the new rating is more than 20% worse than the last rating, break the loop
                        if (pointCandidate.Rating < lastRating * 0.8m)
                        {
                            break;
                        }
                        //keep only the best candidate within a distance of 2.000 m on each side
                        if (chosenPointCandidates.Any(c => Math.Abs(c.DistanceOnLine - pointCandidate.DistanceOnLine) < 1000))
                        {
                            continue;
                        }
                        //if there is already an obviously better candidate (lower length AND more distanceOnLine, then skip as well
                        //if (chosenPointCandidates.Any(c => c.Length < pointCandidate.Length && c.DistanceOnLine > pointCandidate.DistanceOnLine))
                        //    continue;

                        chosenPointCandidates.Add(pointCandidate);
                        pointCandidateLimit--;
                        lastRating = pointCandidate.Rating;
                    }
                    candidates.AddRange(chosenPointCandidates);
                }
                //remove one more point from the endpoints, so the first one to check against will be at least 1000m ahead again
                if (endpoints.Keys.Count > 0)
                {
                    endpoints.Remove(endpoints.Keys.First());
                }
            }
            //now make sure the same applies for the endpoints:
            foreach (var endPointID in candidates.Select(c => c.EndPointID).Distinct().ToList())
            {
                var endpointCandidates = candidates.Where(c => c.EndPointID == endPointID || c.StartPointID == endPointID).ToList();
                foreach (var endPointCandidate in endpointCandidates)
                {
                    //keep only the best candidate of this endpoint within a range of 2.000 m on each side
                    if (endpointCandidates.Any(c => c.Rating > endPointCandidate.Rating && Math.Abs(c.DistanceOnLine - endPointCandidate.DistanceOnLine) < 2000))
                    {
                        candidates.Remove(endPointCandidate);
                    }
                    //remove, if there are already obviously better candidates (lower length AND more distanceOnLine)
                    //This also removes potentially good candidates further away... this could be restricted by a distance limit...
                    else if (candidates.Any(c => c.Length < endPointCandidate.Length && c.DistanceOnLine > endPointCandidate.DistanceOnLine && c.StartPointID <= endPointCandidate.StartPointID && c.EndPointID >= endPointCandidate.EndPointID))
                    {
                        candidates.Remove(endPointCandidate);
                    }
                }
            }
            int vicinity = 1000 / pointsIntervalOnContour;
            //select all candidates that are in the vicinity of another candidate on the same contour but are lower ranked
            //this can only be selected for now that we are sure to have true dam candidates in the list
            var candidatesToDelete = candidates.Where(c => candidates.Any(d => d.ContourID == c.ContourID && d != c && c.Rating < d.Rating &&
                                                                          ((Math.Abs(c.StartPointID - d.StartPointID) < vicinity && Math.Abs(c.EndPointID - d.EndPointID) < vicinity) ||
                                                                           (Math.Abs(c.StartPointID - d.EndPointID) < vicinity && Math.Abs(c.EndPointID - d.StartPointID) < vicinity)
                                                                          )
                                                                          )
                                                      ).ToList();

            foreach (var candidateToDelete in candidatesToDelete)
            {
                candidates.Remove(candidateToDelete);
            }
            //if the contour is a closed loop, we have to have another look at neighbors across the start/end of the contour
            if (closedLoop)
            {
                var candidatesToDelete2 = candidates.Where(c => candidates.Any(d => d.ContourID == c.ContourID && d != c && c.Rating < d.Rating &&
                                                                               (((points.Count - Math.Abs(c.StartPointID - d.StartPointID)) < vicinity && Math.Abs(c.EndPointID - d.EndPointID) < vicinity)
                                                                                ||
                                                                                (Math.Abs(c.StartPointID - d.StartPointID) < vicinity && (points.Count - Math.Abs(c.EndPointID - d.EndPointID)) < vicinity)
                                                                                ||
                                                                                ((points.Count - Math.Abs(c.StartPointID - d.EndPointID)) < vicinity && Math.Abs(c.EndPointID - d.StartPointID) < vicinity)
                                                                                ||
                                                                                (Math.Abs(c.StartPointID - d.EndPointID) < vicinity && (points.Count - Math.Abs(c.EndPointID - d.StartPointID)) < vicinity)
                                                                               )
                                                                               )
                                                           ).ToList();
                foreach (var candidateToDelete in candidatesToDelete2)
                {
                    candidates.Remove(candidateToDelete);
                }
            }
            return(candidates);
        }
Example #4
0
        protected override async void OnClick()
        {
            SharedFunctions.Log("DamVolume Calculation started");
            DateTime startTime = DateTime.Now;
            await Project.Current.SaveEditsAsync();

            IncomingCandidates = 0;
            OutgoingCandidates = 0;
            List <CandidateDam> candidates        = new List <CandidateDam>();
            List <CandidateDam> candidates2Delete = new List <CandidateDam>();

            try
            {
                await QueuedTask.Run(async() =>
                {
                    if (!SharedFunctions.LayerExists("DamCandidates") || !SharedFunctions.LayerExists("Contours"))
                    {
                        return;
                    }
                    var _damCandidatesLayer = MapView.Active.Map.FindLayers("DamCandidates").FirstOrDefault();

                    BasicFeatureLayer damCandidatesLayer = _damCandidatesLayer as BasicFeatureLayer;
                    SharedFunctions.LoadDamCandidatesFromLayer(candidates, damCandidatesLayer);
                    IncomingCandidates = candidates.Count();
                    OutgoingCandidates = IncomingCandidates;

                    //create stacked profile
                    string inputDEM  = Parameter.DEMCombo.SelectedItem.ToString();
                    var valueArray   = Geoprocessing.MakeValueArray(damCandidatesLayer.Name, inputDEM, "DamProfile", null);
                    var environments = Geoprocessing.MakeEnvironmentArray(overwriteoutput: true);
                    var cts          = new CancellationTokenSource();
                    await Project.Current.SaveEditsAsync();
                    var res = await Geoprocessing.ExecuteToolAsync("StackProfile", valueArray, environments, cts.Token, null, GPExecuteToolFlags.Default);

                    //analyse profile and add data to line feature or delete line feature
                    //profile can be used to calculate frontal dam area and dam volume?!
                    var damProfileTable = MapView.Active.Map.FindStandaloneTables("DamProfile").FirstOrDefault();
                    if (damProfileTable == null)
                    {
                        SharedFunctions.Log("No DamProfile Table found!");
                    }

                    CandidateDam cand = null;
                    double prev_dist  = 0;
                    int prev_lineID   = -1;
                    using (var profileCursor = damProfileTable.Search())
                    {
                        while (profileCursor.MoveNext())
                        {
                            using (Row profileRow = profileCursor.Current)
                            {
                                var first_dist = (double)profileRow[1];
                                var first_z    = (double)profileRow[2];
                                var line_ID    = (int)profileRow[5];

                                if (prev_lineID != line_ID)
                                {
                                    prev_dist = 0;
                                    cand      = candidates.SingleOrDefault(c => c.ObjectID == line_ID);
                                    // set Volume and ZMin to the default values
                                    cand.DamVolume = 0;
                                    cand.ZMin      = cand.ContourHeight;
                                }
                                else if (candidates2Delete.Contains(cand))
                                {
                                    continue;
                                }
                                //set lowest point of dam to calculate max dam height later
                                if (cand.ZMin > (int)first_z)
                                {
                                    cand.ZMin = (int)first_z;
                                }

                                if ((int)first_z > (cand.ContourHeight + 5))
                                {
                                    candidates2Delete.Add(cand);
                                    continue;
                                }

                                //add volume of current block of dam... the assumption is a triangle dam shape (cross section) with alpha = 45°
                                //thus the area of the cross section is calculated by <height²> and the volume by <height² * length>
                                cand.DamVolume += (long)(Math.Pow((cand.ContourHeight - first_z), 2) * (first_dist - prev_dist));

                                prev_lineID = line_ID;
                                prev_dist   = first_dist;
                            }
                        }
                    }

                    await DeleteCandidates(candidates, damCandidatesLayer, candidates2Delete);
                    //remove candidates with dam heights outside of the limits
                    await DeleteCandidates(candidates, damCandidatesLayer, candidates.Where(c => (c.ContourHeight - c.ZMin) < 10 || (c.ContourHeight - c.ZMin) > 300).ToList());

                    //update the new attributes to the feature
                    foreach (var candidate in candidates)
                    {
                        var editOp2 = new EditOperation();
                        Dictionary <string, object> attributes = new Dictionary <string, object>();
                        attributes.Add("DamHeight", (short)(candidate.ContourHeight - candidate.ZMin));
                        attributes.Add("DamVolume", (long)(candidate.DamVolume));
                        editOp2.Modify(damCandidatesLayer, candidate.ObjectID, attributes);
                        var resu = await editOp2.ExecuteAsync();
                    }

                    await Project.Current.SaveEditsAsync();
                });
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
            finally
            {
                DateTime endTime = DateTime.Now;
                SharedFunctions.Log("Analysed in " + (endTime - startTime).TotalSeconds.ToString("N") + " seconds completed (" + OutgoingCandidates.ToString("N0") + " of " + IncomingCandidates.ToString("N0") + " candidates left)");
            }
        }
        private async static Task <bool> FindPairs(BasicFeatureLayer reservoirPairsLayer, BasicFeatureLayer reservoirSurfacesLayer, BasicFeatureLayer damLayer)
        {
            List <ReservoirPair> reservoirPairs = new List <ReservoirPair>();
            List <CandidateDam>  res1List       = new List <CandidateDam>();

            SharedFunctions.LoadDamCandidatesFromLayer(res1List, damLayer);
            List <ReservoirSurface> reservoirSurfaceList = new List <ReservoirSurface>();

            SharedFunctions.LoadReservoirSurfacesFromLayer(reservoirSurfaceList, reservoirSurfacesLayer);
            List <CandidateDam> res2List = new List <CandidateDam>(res1List);
            int analyzedCounter          = 0;
            int createdCounter           = 0;

            foreach (var dam1 in res1List)
            {
                res2List.Remove(dam1);
                foreach (var dam2 in res2List)
                {
                    try
                    {
                        analyzedCounter++;
                        CandidateDam lowerDam = null;
                        CandidateDam upperDam = null;
                        if (dam1.ContourHeight > dam2.ContourHeight)
                        {
                            lowerDam = dam2;
                            upperDam = dam1;
                        }
                        else
                        {
                            lowerDam = dam1;
                            upperDam = dam2;
                        }

                        //check for height-difference:
                        int usableHeightDifference = upperDam.ContourHeight - upperDam.DamHeight - lowerDam.ContourHeight;
                        if (usableHeightDifference < 50)
                        {
                            continue;
                        }

                        //check for horizontal distance
                        Coordinate3D lowerDamCenter            = new Coordinate3D((lowerDam.StartPoint.X + lowerDam.EndPoint.X) / 2, (lowerDam.StartPoint.Y + lowerDam.EndPoint.Y) / 2, (lowerDam.StartPoint.Z + lowerDam.EndPoint.Z) / 2);
                        Coordinate3D upperDamCenter            = new Coordinate3D((upperDam.StartPoint.X + upperDam.EndPoint.X) / 2, (upperDam.StartPoint.Y + upperDam.EndPoint.Y) / 2, (upperDam.StartPoint.Z + upperDam.EndPoint.Z) / 2);
                        decimal      distanceBetweenDamCenters = SharedFunctions.DistanceBetween(lowerDamCenter, upperDamCenter);
                        if (distanceBetweenDamCenters > 5000)
                        {
                            continue;
                        }

                        //check for reservoir overlap //NOT NECCESSARY due to height-difference check, where all corresponding cases are already filtered
                        //if (GeometryEngine.Instance.Overlaps(reservoirSurfaceList.Single(c => c.DamID == dam1.ObjectID).Polygon, reservoirSurfaceList.Single(c => c.DamID == dam1.ObjectID).Polygon))
                        //    continue;

                        //calculate CapacityInMWh:
                        long usableVolume = upperDam.ReservoirVolume;
                        if (lowerDam.ReservoirVolume < usableVolume)
                        {
                            usableVolume = lowerDam.ReservoirVolume;
                        }

                        //only assume 85% of the water as usable in reality
                        usableVolume = (long)(0.85 * usableVolume);
                        float capacityInMWh = (float)(1000 * 9.8 * usableHeightDifference * usableVolume * 0.9) / (float)((long)3600 * 1000000);

                        //calculate utilizationPercentage:
                        float capacityUtilization = 0;
                        if (upperDam.ReservoirVolume != 0)
                        {
                            capacityUtilization = (float)lowerDam.ReservoirVolume / upperDam.ReservoirVolume;
                        }
                        if (capacityUtilization > 1)
                        {
                            capacityUtilization = 1 / (float)capacityUtilization;
                        }

                        //check for Utilization of at least 75%
                        if (capacityUtilization < 0.75)
                        {
                            continue;
                        }

                        decimal capacityDistanceRatio = (decimal)capacityInMWh * 100 / distanceBetweenDamCenters;

                        List <Coordinate3D> coordinates = new List <Coordinate3D>()
                        {
                            lowerDamCenter, upperDamCenter
                        };
                        Polyline polyline = PolylineBuilder.CreatePolyline(coordinates);

                        ReservoirPair reservoirPair = new ReservoirPair();
                        reservoirPair.LowerDam               = lowerDam;
                        reservoirPair.UpperDam               = upperDam;
                        reservoirPair.LowerDamCenter         = lowerDamCenter;
                        reservoirPair.UpperDamCenter         = upperDamCenter;
                        reservoirPair.Polyline               = polyline;
                        reservoirPair.LowerDamID             = lowerDam.ObjectID;
                        reservoirPair.UpperDamID             = upperDam.ObjectID;
                        reservoirPair.CapacityInMWh          = capacityInMWh;
                        reservoirPair.Distance               = distanceBetweenDamCenters;
                        reservoirPair.LowerHeight            = lowerDam.ContourHeight;
                        reservoirPair.UpperHeight            = upperDam.ContourHeight;
                        reservoirPair.CapacityDistanceRatio  = capacityDistanceRatio;
                        reservoirPair.UsableHeightDifference = usableHeightDifference;
                        reservoirPair.CapacityUtilization    = capacityUtilization;

                        reservoirPairs.Add(reservoirPair);
                    }
                    catch (Exception ex)
                    {
                        SharedFunctions.Log("Error for attempted Pair with dam1: " + dam1.ObjectID + " and dam2: " + dam2.ObjectID + " (" + ex.Message + ")");
                    }
                }
            }
            //try to further minimize the reservoirPairs selection by only keeping
            //the best connection within a buffer of 100 m (around both dam center points)
            List <ReservoirPair> pairsToDelete = new List <ReservoirPair>();

            foreach (var reservoirPair in reservoirPairs.ToList())
            {
                if (pairsToDelete.Contains(reservoirPair))
                {
                    continue;
                }
                var similarPairs = reservoirPairs.Where(c => SharedFunctions.DistanceBetween(reservoirPair.LowerDamCenter, c.LowerDamCenter) <= 100 &&
                                                        SharedFunctions.DistanceBetween(reservoirPair.UpperDamCenter, c.UpperDamCenter) <= 100).ToList();
                if (similarPairs.Count > 1)
                {
                    var bestPair = similarPairs.OrderByDescending(c => c.CapacityDistanceRatio * (decimal)c.CapacityUtilization).First();
                    similarPairs.Remove(bestPair);
                    pairsToDelete = pairsToDelete.Union(similarPairs).ToList();
                }
            }
            foreach (var pairToDelete in pairsToDelete)
            {
                reservoirPairs.Remove(pairToDelete);
            }

            //insert the remaining objects into the DB
            foreach (var reservoirPair in reservoirPairs)
            {
                var attributes = new Dictionary <string, object>
                {
                    { "Shape", reservoirPair.Polyline },
                    { "LowerDamID", (long)reservoirPair.LowerDamID },
                    { "UpperDamID", (long)reservoirPair.UpperDamID },
                    { "CapacityInMWh", (float)reservoirPair.CapacityInMWh },
                    { "Distance", (long)reservoirPair.Distance },
                    { "LowerHeight", (short)reservoirPair.LowerHeight },
                    { "UpperHeight", (short)reservoirPair.UpperHeight },
                    { "CapacityDistanceRatio", (float)reservoirPair.CapacityDistanceRatio },
                    { "UsableHeightDifference", (short)reservoirPair.UsableHeightDifference },
                    { "CapacityUtilization", (float)reservoirPair.CapacityUtilization }
                };
                var createOperation = new EditOperation()
                {
                    Name = "Create reservoir pair", SelectNewFeatures = false
                };
                createOperation.Create(reservoirPairsLayer, attributes);
                await createOperation.ExecuteAsync();

                createdCounter++;
            }

            SharedFunctions.Log(analyzedCounter + " combinations analysed and " + createdCounter + " viable pairs found");

            await Project.Current.SaveEditsAsync();

            return(true);
        }