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); }
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(); }