Example #1
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();
        }
        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);
        }
Example #3
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 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();
        }