Beispiel #1
0
        protected override async void OnClick()
        {
            if (!SharedFunctions.LayerExists("TIN") || !SharedFunctions.LayerExists("DamCandidates") || !SharedFunctions.LayerExists("ReservoirSurfaces"))
            {
                return;
            }

            await Project.Current.SaveEditsAsync();

            string TINLayer               = "TIN";
            string damCandidatesLayer     = "DamCandidates";
            string reservoirSurfacesLayer = "ReservoirSurfaces";
            var    args = Geoprocessing.MakeValueArray(reservoirSurfacesLayer, damCandidatesLayer, TINLayer, damCandidatesLayer);
            await SharedFunctions.RunModel(args, "Reservoir Volume");
        }
        public async static Task <BasicFeatureLayer> CreateDamFeatureClass(string name)
        {
            var existingLayer = MapView.Active.Map.FindLayers(name).FirstOrDefault();

            if (existingLayer != null)
            {
                return(existingLayer as BasicFeatureLayer);
            }

            SharedFunctions.Log("Creating DamCandidates layer");
            List <object> arguments = new List <object> {
                CoreModule.CurrentProject.DefaultGeodatabasePath, name, "POLYLINE", "", "DISABLED", "ENABLED"
            };

            arguments.Add(SpatialReference);
            IGPResult result = await Geoprocessing.ExecuteToolAsync("CreateFeatureclass_management", Geoprocessing.MakeValueArray(arguments.ToArray()));

            var layer = MapView.Active.Map.FindLayers(name).FirstOrDefault() as BasicFeatureLayer;
            await SharedFunctions.ExecuteAddFieldTool(layer, "ContourID", "LONG");

            await SharedFunctions.ExecuteAddFieldTool(layer, "StartPointID", "LONG");

            await SharedFunctions.ExecuteAddFieldTool(layer, "EndPointID", "LONG");

            await SharedFunctions.ExecuteAddFieldTool(layer, "ContourHeight", "SHORT");

            await SharedFunctions.ExecuteAddFieldTool(layer, "LengthRating", "FLOAT");

            await SharedFunctions.ExecuteAddFieldTool(layer, "DistanceOnLine", "LONG");

            await SharedFunctions.ExecuteAddFieldTool(layer, "Length", "SHORT");

            await SharedFunctions.ExecuteAddFieldTool(layer, "StartPointDistance", "LONG");

            await SharedFunctions.ExecuteAddFieldTool(layer, "EndPointDistance", "LONG");

            await SharedFunctions.ExecuteAddFieldTool(layer, "DamHeight", "SHORT");

            await SharedFunctions.ExecuteAddFieldTool(layer, "DamVolume", "LONG");

            await SharedFunctions.ExecuteAddFieldTool(layer, "ReservoirVolume", "LONG");

            await SharedFunctions.ExecuteAddFieldTool(layer, "VolumeRating", "FLOAT");

            await SharedFunctions.ExecuteAddFieldTool(layer, "DamSpansContourStart", "SHORT");

            return(layer);
        }
Beispiel #3
0
        public async static Task <BasicFeatureLayer> CreatePolygonFeatureClass(string name)
        {
            var existingLayer = MapView.Active.Map.FindLayers(name).FirstOrDefault();

            if (existingLayer != null)
            {
                return(existingLayer as BasicFeatureLayer);
            }
            List <object> arguments = new List <object> {
                CoreModule.CurrentProject.DefaultGeodatabasePath, name, "POLYGON", "", "DISABLED", "ENABLED"
            };

            arguments.Add(SpatialReference);
            IGPResult result = await Geoprocessing.ExecuteToolAsync("CreateFeatureclass_management", Geoprocessing.MakeValueArray(arguments.ToArray()));

            var layer = MapView.Active.Map.FindLayers(name).FirstOrDefault() as BasicFeatureLayer;
            await SharedFunctions.ExecuteAddFieldTool(layer, "DamID", "LONG");

            return(layer);
        }
        private static void AnalyseContourHeights(Dictionary <int, SortedDictionary <int, SortedDictionary <long, MapPoint> > > contourHeights, CancellationToken ctoken)
        {
            int selectedCandidates = 0;

            foreach (var contourHeight in contourHeights)
            {
                //Analyse lines of one ContourLine
                foreach (var contourID in contourHeight.Value)
                {
                    //skip contours with less than 2.000 m length
                    if (contourID.Value.Count < (int)2000 / pointsIntervalOnContour)
                    {
                        continue;
                    }
                    List <CandidateDam> candidates = AnalyseCountourPoints(contourID.Value, contourHeight.Key, contourID.Key, ctoken);
                    lock (lockingObject)
                    {
                        chosenCandidates.AddRange(candidates);
                        selectedCandidates += candidates.Count;
                    }
                }
                SharedFunctions.Log(selectedCandidates.ToString("N0") + " candidates selected for " + contourHeight.Key + "m contours (Total: " + chosenCandidates.Count.ToString("N0") + " of " + PotentialCandidates.ToString("N0") + " potentials)");
            }
        }
Beispiel #5
0
        protected override async void OnClick()
        {
            SharedFunctions.Log("Starting To Create 3D Visualization");
            DateTime startTime = DateTime.Now;

            CIMLineSymbol      symbol          = SymbolFactory.Instance.ConstructLineSymbol(ColorFactory.Instance.GreyRGB, 5.0, SimpleLineStyle.Solid);
            CIMSymbolReference symbolReference = symbol.MakeSymbolReference();

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

                    SpatialReference      = damCandidatesLayer.GetSpatialReference();
                    var damVisLayer       = await CreateMultiPatchFeatureClass("DamVisualization");
                    var reservoirVisLayer = await CreatePolygonFeatureClass("ReservoirVisualization");

                    Visualize(damVisLayer, reservoirVisLayer, reservoirSurfacesLayer as BasicFeatureLayer, damCandidatesLayer as BasicFeatureLayer);
                });
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
            finally
            {
                DateTime endTime = DateTime.Now;
                SharedFunctions.Log("Visualized in " + (endTime - startTime).TotalSeconds.ToString() + " seconds");
            }
        }
        public async static Task <BasicFeatureLayer> CreateFeatureClass(string name)
        {
            var existingLayer = MapView.Active.Map.FindLayers(name).FirstOrDefault();

            if (existingLayer != null)
            {
                return(existingLayer as BasicFeatureLayer);
            }
            List <object> arguments = new List <object> {
                CoreModule.CurrentProject.DefaultGeodatabasePath, name, "POLYLINE", "", "DISABLED", "ENABLED"
            };

            arguments.Add(SpatialReference);
            IGPResult result = await Geoprocessing.ExecuteToolAsync("CreateFeatureclass_management", Geoprocessing.MakeValueArray(arguments.ToArray()));

            var layer = MapView.Active.Map.FindLayers(name).FirstOrDefault() as BasicFeatureLayer;
            await SharedFunctions.ExecuteAddFieldTool(layer, "LowerDamID", "LONG");

            await SharedFunctions.ExecuteAddFieldTool(layer, "UpperDamID", "LONG");

            await SharedFunctions.ExecuteAddFieldTool(layer, "CapacityInMWh", "FLOAT");

            await SharedFunctions.ExecuteAddFieldTool(layer, "Distance", "LONG");

            await SharedFunctions.ExecuteAddFieldTool(layer, "LowerHeight", "SHORT");

            await SharedFunctions.ExecuteAddFieldTool(layer, "UpperHeight", "SHORT");

            await SharedFunctions.ExecuteAddFieldTool(layer, "CapacityDistanceRatio", "FLOAT");

            await SharedFunctions.ExecuteAddFieldTool(layer, "UsableHeightDifference", "SHORT");

            await SharedFunctions.ExecuteAddFieldTool(layer, "CapacityUtilization", "FLOAT");

            return(layer);
        }
        protected override async void OnClick()
        {
            SharedFunctions.Log("Starting To Create Reservoir Polygons");
            DateTime startTime = DateTime.Now;

            surfaces = new List <ReservoirSurface>();

            try
            {
                await Project.Current.SaveEditsAsync();

                await QueuedTask.Run(async() =>
                {
                    if (!SharedFunctions.LayerExists("DamCandidates") || !SharedFunctions.LayerExists("Contours"))
                    {
                        return;
                    }
                    damLayer     = MapView.Active.Map.FindLayers("DamCandidates").FirstOrDefault() as BasicFeatureLayer;
                    contourLayer = MapView.Active.Map.FindLayers("Contours").FirstOrDefault() as BasicFeatureLayer;

                    SpatialReference       = damLayer.GetSpatialReference();
                    reservoirSurfacesLayer = await CreatePolygonFeatureClass("ReservoirSurfaces");

                    ConstructPolygons();
                });
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
            finally
            {
                DateTime endTime = DateTime.Now;
                SharedFunctions.Log("Analysed in " + (endTime - startTime).TotalSeconds.ToString() + " seconds");
            }
        }
Beispiel #8
0
 private void ClearLog()
 {
     SharedFunctions.ClearLog();
 }
        protected override async void OnClick()
        {
            SharedFunctions.Log("Search for candidate dams started");
            pointsIntervalOnContour = Convert.ToInt32(Parameter.PointIntervalBox.Text);
            DateTime startTime = DateTime.Now;

            chosenCandidates    = new List <CandidateDam>();
            PotentialCandidates = 0;

            var pd = new ProgressDialog("Search for candidate dams", "Canceled", 100, false);

            cps = new CancelableProgressorSource(pd);
            cps.Progressor.Max = 100;
            PointsAnalyzed     = 0;
            TotalPointsCount   = 0;

            try
            {
                await Project.Current.SaveEditsAsync();

                BasicFeatureLayer layer = null;

                await QueuedTask.Run(async() =>
                {
                    if (!SharedFunctions.LayerExists("ContourPoints"))
                    {
                        return;
                    }

                    CancellationToken ctoken = new CancellationToken();

                    //create line feature layer if it does not exist
                    BasicFeatureLayer damCandidatesLayer = await CreateDamFeatureClass("DamCandidates");

                    var contourPointsLayer = MapView.Active.Map.FindLayers("ContourPoints").FirstOrDefault();
                    layer = contourPointsLayer as BasicFeatureLayer;

                    // store the spatial reference of the current layer
                    SpatialReference = layer.GetSpatialReference();

                    //Cursor for selected points
                    RowCursor cursor = layer.GetSelection().Search();

                    //If no selection was set, use full points layer
                    if (layer.GetSelection().GetCount() == 0)
                    {
                        cursor = layer.Search();
                    }

                    Dictionary <int, SortedDictionary <int, SortedDictionary <long, MapPoint> > > contourHeights = new Dictionary <int, SortedDictionary <int, SortedDictionary <long, MapPoint> > >();

                    cps.Progressor.Status = "Loading ContourPoints into memory";
                    SharedFunctions.Log("Loading all ContourPoints into memory");
                    while (cursor.MoveNext())
                    {
                        if (ctoken.IsCancellationRequested)
                        {
                            SharedFunctions.Log("Canceled");
                            return;
                        }
                        using (Row row = cursor.Current)
                        {
                            var point         = row[1] as MapPoint;
                            var pointID       = (int)row[0];
                            var contourHeight = (int)(double)row[4];
                            var contourID     = (int)row[2];

                            if (!ContourLengths.ContainsKey(contourID))
                            {
                                ContourLengths.Add(contourID, (double)row["Shape_Length"]);
                            }
                            if (!contourHeights.ContainsKey((int)contourHeight))
                            {
                                contourHeights.Add((int)contourHeight, new SortedDictionary <int, SortedDictionary <long, MapPoint> >());
                            }
                            if (!contourHeights[contourHeight].ContainsKey((int)contourID))
                            {
                                contourHeights[contourHeight].Add((int)contourID, new SortedDictionary <long, MapPoint>());
                            }
                            contourHeights[contourHeight][(int)contourID].Add(pointID, point);
                            TotalPointsCount++;
                        }
                    }
                    cps.Progressor.Status = "Analyze Contours";
                    SharedFunctions.Log("Analyze Contours");
                    bool multiThreading = (Parameter.MultiThreadingBox == null || !Parameter.MultiThreadingBox.IsChecked.HasValue || Parameter.MultiThreadingBox.IsChecked.Value);
                    if (multiThreading)
                    {
                        HeightsToProcess = contourHeights.Keys.ToList();
                        int ThreadCount  = Math.Min(HeightsToProcess.Count, Environment.ProcessorCount);
                        SharedFunctions.Log("Divided work into " + ThreadCount + " threads...");
                        await Task.WhenAll(Enumerable.Range(1, ThreadCount).Select(c => Task.Run(
                                                                                       () =>
                        {
                            while (HeightsToProcess.Count > 0)    // && !ctoken.IsCancellationRequested)
                            {
                                int height = -1;
                                lock (HeightsToProcess)
                                {
                                    height = HeightsToProcess.FirstOrDefault();
                                    if (height != 0)
                                    {
                                        HeightsToProcess.Remove(height);
                                    }
                                }
                                if (height != -1)
                                {
                                    var calc = new Dictionary <int, SortedDictionary <int, SortedDictionary <long, MapPoint> > >();
                                    calc.Add(height, contourHeights[height]);
                                    AnalyseContourHeights(calc, ctoken);
                                }
                            }
                        }
                                                                                       , ctoken))
                                           );
                    }
                    else
                    {
                        //Single Thread:
                        AnalyseContourHeights(contourHeights, ctoken);
                    }
                    cps.Progressor.Status = "Saving all " + chosenCandidates.Count + " candidates";
                    SharedFunctions.Log("Saving all " + chosenCandidates.Count + " candidates");
                    foreach (var candidate in chosenCandidates)
                    {
                        if (ctoken.IsCancellationRequested)
                        {
                            SharedFunctions.Log("Canceled");
                            return;
                        }
                        //Create coordinates for Polyline Feature with height ContourHeight + 5 Meters!
                        List <Coordinate3D> coordinates = new List <Coordinate3D>()
                        {
                            new Coordinate3D(candidate.StartPoint.X, candidate.StartPoint.Y, candidate.ContourHeight + 5),
                            new Coordinate3D(candidate.EndPoint.X, candidate.EndPoint.Y, candidate.ContourHeight + 5)
                        };

                        //save all selected candidates into the db
                        var attributes = new Dictionary <string, object>
                        {
                            { "Shape", PolylineBuilder.CreatePolyline(coordinates) },
                            { "ContourID", (long)candidate.ContourID },
                            { "StartPointID", (long)candidate.StartPointID },
                            { "EndPointID", (long)candidate.EndPointID },
                            { "ContourHeight", (short)candidate.ContourHeight },
                            { "LengthRating", (float)candidate.Rating },
                            { "DistanceOnLine", (long)candidate.DistanceOnLine },
                            { "Length", (short)candidate.Length },
                            { "StartPointDistance", (long)candidate.StartPointDistance },
                            { "EndPointDistance", (long)candidate.EndPointDistance },
                            { "DamSpansContourStart", (short)(candidate.DamSpansContourStart ? 1 : 0) }
                        };
                        var editOp = new EditOperation()
                        {
                            Name = "Create dam candidate", SelectNewFeatures = false
                        };
                        editOp.Create(damCandidatesLayer, attributes);
                        ////Execute the operations
                        editOp.Execute();
                    }
                }, cps.Progressor);

                //save all edits
                await Project.Current.SaveEditsAsync();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
            finally
            {
                DateTime endTime = DateTime.Now;
                SharedFunctions.Log("Analysed " + PotentialCandidates.ToString("N0") + " candidates ( " + chosenCandidates.Count.ToString("N0") + " selected) in " + (endTime - startTime).TotalSeconds.ToString("N") + " seconds");
            }
        }
Beispiel #10
0
        private async static void Visualize(BasicFeatureLayer dam3dLayer, BasicFeatureLayer reservoirVisLayer, BasicFeatureLayer reservoirSurfacesLayer, BasicFeatureLayer damLayer)
        {
            SharedFunctions.DeleteAllFeatures(reservoirVisLayer);
            SharedFunctions.DeleteAllFeatures(dam3dLayer);

            List <long> damIDs = new List <long>();
            var         reservoirPairsLayer = MapView.Active.Map.FindLayers("ReservoirPairs").FirstOrDefault();

            if (reservoirPairsLayer != null && ((BasicFeatureLayer)reservoirPairsLayer).SelectionCount > 0)
            {
                var reservoirPairsCursor = ((BasicFeatureLayer)reservoirPairsLayer).GetSelection().Search();
                while (reservoirPairsCursor.MoveNext())
                {
                    using (Row row = reservoirPairsCursor.Current)
                    {
                        int damId = (int)row["LowerDamId"];
                        if (!damIDs.Contains(damId))
                        {
                            damIDs.Add(damId);
                        }
                        damId = (int)row["UpperDamId"];
                        if (!damIDs.Contains(damId))
                        {
                            damIDs.Add(damId);
                        }
                    }
                }
            }
            if (damLayer.SelectionCount > 0)
            {
                var damCursor = damLayer.GetSelection().Search();
                while (damCursor.MoveNext())
                {
                    using (Row row = damCursor.Current)
                    {
                        int damId = (int)row["ObjectID"];
                        if (!damIDs.Contains(damId))
                        {
                            damIDs.Add(damId);
                        }
                    }
                }
            }
            List <CandidateDam> candidates = new List <CandidateDam>();

            SharedFunctions.LoadDamCandidatesFromLayer(candidates, damLayer);
            foreach (var dam in candidates.Where(c => damIDs.Contains(c.ObjectID)).ToList())
            {
                double contourHeight = dam.ContourHeight;

                try
                {
                    MapPoint startPoint = dam.StartPoint;
                    MapPoint endPoint   = dam.EndPoint;
                    double   damHeight  = (double)dam.DamHeight;
                    double   damLength  = (double)dam.Length;

                    Coordinate3D coord1 = startPoint.Coordinate3D;
                    int          factor = ((startPoint.Y <endPoint.Y && startPoint.X> endPoint.X) ||
                                           (startPoint.Y > endPoint.Y && startPoint.X < endPoint.X)
                                           ) ? 1 : -1;
                    Coordinate3D coord2 = new Coordinate3D(coord1.X + factor * damHeight / damLength * Math.Abs(startPoint.Y - endPoint.Y) //X
                                                           , coord1.Y + damHeight / damLength * Math.Abs(startPoint.X - endPoint.X)        //Y
                                                           , coord1.Z - damHeight);                                                        //Z
                    Coordinate3D coord3 = new Coordinate3D(coord1.X - factor * damHeight / damLength * Math.Abs(startPoint.Y - endPoint.Y) //X
                                                           , coord1.Y - damHeight / damLength * Math.Abs(startPoint.X - endPoint.X)        //Y
                                                           , coord1.Z - damHeight);                                                        //Z

                    //Workaround for Bug in ArcGIS Pro 2.4.1: if values are equal, extrusion will fail
                    coord2.X += 0.1;
                    coord2.Y += 0.1;
                    coord3.X += 0.1;
                    coord3.Y += 0.1;
                    List <Coordinate3D> coords = new List <Coordinate3D>();
                    coords.Add(coord1);
                    coords.Add(coord2);
                    coords.Add(coord3);

                    var          newPolygon3D = PolygonBuilder.CreatePolygon(coords, SpatialReference);
                    Coordinate3D coord        = new Coordinate3D(endPoint.X - startPoint.X, endPoint.Y - startPoint.Y, 0.1);
                    var          multipatch   = GeometryEngine.Instance.ConstructMultipatchExtrudeAlongVector3D(newPolygon3D, coord);
                    var          attributes2  = new Dictionary <string, object>
                    {
                        { "Shape", multipatch },
                        { "DamID", (long)dam.ObjectID }
                    };
                    var createOperation2 = new EditOperation()
                    {
                        Name = "Create multipatch", SelectNewFeatures = false
                    };
                    createOperation2.Create(dam3dLayer, attributes2);
                    await createOperation2.ExecuteAsync();

                    //add SurfacePolygon to Visualization:
                    var queryFilter = new QueryFilter {
                        WhereClause = string.Format("DamID = {0}", dam.ObjectID)
                    };
                    var surfaceCursor = reservoirSurfacesLayer.Select(queryFilter).Search();
                    if (surfaceCursor.MoveNext())
                    {
                        using (Row row = surfaceCursor.Current)
                        {
                            var polygon = (row as Feature).GetShape() as Polygon;
                            attributes2 = new Dictionary <string, object>
                            {
                                { "Shape", polygon },
                                { "DamID", (long)dam.ObjectID }
                            };
                            var createOperationSurface = new EditOperation()
                            {
                                Name = "Create surface", SelectNewFeatures = false
                            };
                            createOperationSurface.Create(reservoirVisLayer, attributes2);
                            await createOperationSurface.ExecuteAsync();
                        }
                    }
                }
                catch (Exception ex)
                {
                    SharedFunctions.Log("Error for 3D Dam with DamID " + dam.ObjectID + ": " + ex.Message);
                }

                SharedFunctions.Log("3D Dam created for Dam " + dam.ObjectID);
            }
            await Project.Current.SaveEditsAsync();
        }
        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);
        }
        private async static void ConstructPolygons()
        {
            List <CandidateDam> candidates = new List <CandidateDam>();

            SharedFunctions.LoadDamCandidatesFromLayer(candidates, damLayer);
            List <Contour> contours = new List <Contour>();

            SharedFunctions.LoadContoursFromLayer(contours, contourLayer);
            //select only contours, that actually have candidate dams on it
            contours = contours.Where(c => candidates.Any(d => d.ContourID == c.ObjectID)).ToList();
            bool multiThreading = (Parameter.MultiThreadingBox == null || !Parameter.MultiThreadingBox.IsChecked.HasValue || Parameter.MultiThreadingBox.IsChecked.Value);

            //ArcGIS.Core.Geometry Tools currently don't seem to support multithreading.
            //Question https://community.esri.com/thread/243147-multithreading-parallel-processing-in-arcgis-pro-addin
            //received no answer so far. Until a solution is found, multithreading logic has to be deactivated
            multiThreading = false;

            if (multiThreading)
            {
                List <PolylineBuilder> polylineBuilders = new List <PolylineBuilder>();
                for (int i = 0; i < Environment.ProcessorCount; i++)
                {
                    polylineBuilders.Add(new PolylineBuilder(SpatialReference));
                }

                ContoursToProcess = contours.Select(c => c.ObjectID).ToList();
                SharedFunctions.Log("Divided work into " + Environment.ProcessorCount + " threads for all logical Processors...");
                await Task.WhenAll(polylineBuilders.Select(c => Task.Run(//Enumerable.Range(1, Environment.ProcessorCount).Select(c => Task.Run(
                                                               async() =>
                {
                    while (ContoursToProcess.Count > 0)
                    {
                        long contourID = -1;
                        lock (ContoursToProcess)
                        {
                            contourID = ContoursToProcess.FirstOrDefault();
                            if (contourID != 0)
                            {
                                ContoursToProcess.Remove(contourID);
                            }
                        }
                        if (contourID != -1)
                        {
                            var calc = new List <Contour>()
                            {
                                contours.Single(d => d.ObjectID == contourID)
                            };
                            await PolygonsForContours(candidates, calc, c);
                        }
                    }
                }
                                                               ))
                                   );
            }
            else
            {
                await PolygonsForContours(candidates, contours, new PolylineBuilder(SpatialReference));
            }

            SharedFunctions.Log("Save all " + surfaces.Count + " surfaces");
            foreach (var surface in surfaces)
            {
                var attributes = new Dictionary <string, object>
                {
                    { "Shape", surface.Polygon },
                    { "DamID", (long)surface.DamID },
                    { "ContourHeight", (short)surface.ContourHeight }
                };
                var createOperation = new EditOperation()
                {
                    Name = "Create reservoir polygon", SelectNewFeatures = false
                };
                createOperation.Create(reservoirSurfacesLayer, attributes);
                await createOperation.ExecuteAsync();
            }
            await Project.Current.SaveEditsAsync();
        }
        private static async Task PolygonsForContours(List <CandidateDam> candidates, List <Contour> contours, PolylineBuilder polylineBuilder)
        {
            foreach (var contour in contours)
            {
                var contourGeometry = contour.Polyline;
                int counter         = 0;
                int contourHeight   = 0;
                foreach (var candidate in candidates.Where(c => c.ContourID == contour.ObjectID).ToList())
                {
                    try
                    {
                        while (polylineBuilder.CountParts > 0)
                        {
                            polylineBuilder.RemovePart(0);
                        }

                        //add the full contour
                        polylineBuilder.AddParts(contourGeometry.Parts);
                        //split at the endpoint
                        polylineBuilder.SplitAtDistance(candidate.EndPointDistance, false, true);

                        if (candidate.DamSpansContourStart)
                        {
                            //remove the part of the contour after the endpoint
                            //split at the startpoint
                            if (candidate.StartPointDistance != 0)
                            {
                                polylineBuilder.SplitAtDistance(candidate.StartPointDistance, false, true);
                                //remove the part of the polyline before the startpoint
                                polylineBuilder.RemovePart(1);
                            }
                            //Handle the situation, when the startpoint is on the very beginning of the contour line
                            else
                            {
                                polylineBuilder.RemovePart(0);
                            }
                        }
                        else
                        {
                            //remove the part of the contour after the endpoint
                            polylineBuilder.RemovePart(1);
                            //split at the startpoint
                            if (candidate.StartPointDistance != 0)
                            {
                                polylineBuilder.SplitAtDistance(candidate.StartPointDistance, false, true);
                                //remove the part of the polyline before the startpoint
                                polylineBuilder.RemovePart(0);
                            }
                        }
                        var newPolygon3D = PolygonBuilder.CreatePolygon(polylineBuilder.ToGeometry().Copy3DCoordinatesToList(), SpatialReference);

                        ReservoirSurface surface = new ReservoirSurface();
                        surface.Polygon       = GeometryEngine.Instance.Move(newPolygon3D, 0, 0, candidate.ContourHeight) as Polygon;
                        surface.DamID         = (long)candidate.ObjectID;
                        surface.ContourHeight = (short)candidate.ContourHeight;
                        surfaces.Add(surface);

                        counter++;
                        contourHeight = candidate.ContourHeight;
                    }
                    catch (Exception ex)
                    {
                        SharedFunctions.Log("Error for DamID " + candidate.ObjectID + " for ContourHeight " + candidate.ContourHeight + ": (" + ex.Message + ")");
                    }
                }

                SharedFunctions.Log("Created " + surfaces.Count.ToString("N0") + " Polygons ... " + counter.ToString("N0") + " for Contour " + contour.ObjectID + " (" + contour.Height + " m)");
            }
        }