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