using DevExpress.Mvvm.Native; using DevExpress.XtraCharts; namespace PBS.WinFrmUI.Hydro { public partial class SystemCurvePage : DocumentPage { public SystemCurvePage() { InitializeComponent(); this.facilityTreeListCtrl1.SelectFacilityEvent += FacilityTreeListCtrl1_SelectFacilityEvent; this.facilityTreeListCtrl1.RefreshDataEvent += () => { RefreshData(); }; } private class ChartPressurePointViewModel { public ChartPressurePointViewModel() { } public ChartPressurePointViewModel(double totalFlow, double upperPressure, double lowerPressure, double averagePressure) { this.TotalFlow = totalFlow; this.UpperPressure = upperPressure; this.LowerPressure = lowerPressure; this.AveragePressure = averagePressure; } public double TotalFlow { get; set; } public double UpperPressure { get; set; } public double LowerPressure { get; set; } public double AveragePressure { get; set; } } private class PointViewModel { public PointViewModel() { } public PointViewModel(double x, double y) { this.X = x; this.Y = y; } public double X { get; set; } public double Y { get; set; } } public class CalcResultViewModel { public double TotalFlow { get; set; } public double EndPressure { get; set; } public List CalcNodeList { get; set; } } public class CalcNodeViewModel { public int NodeIndex { get; set; } public double Elevation { get; set; } public double Flow { get; set; } public double Pressure { get; set; } } private XYDiagram _xyDiagram; private XYDiagramDefaultPane _xyDiagramPane; private Series _seriesWaterPoint; private Series _seriesUpperPressure; private Series _seriesLowerPressure; private Series _seriesAveragePressure; private ConstantLine _constantLineDynamicPressure; private ConstantLine _constantLineStaticPressure; private Yw.Ahart.CubicCurve _curveUpperPressure = null; private Yw.Ahart.CubicCurve _curveLowerPressure = null; private Yw.Ahart.CubicCurve _curveAveragePressure = null; private List _waterPointList = null; private FacilityVmo _facility = null; public override async void InitialDataSource() { var overlay = this.ShowOverlay(); this.chartControl1.Legend.Direction = LegendDirection.LeftToRight; //this.chartControl1.AnimationStartMode = ChartAnimationMode.OnDataChanged; this.chartControl1.RuntimeHitTesting = true; //this.chartControl1.CrosshairEnabled = DevExpress.Utils.DefaultBoolean.False; _xyDiagram = this.chartControl1.Diagram as XYDiagram; _xyDiagramPane = _xyDiagram.DefaultPane; _seriesWaterPoint = this.chartControl1.GetSeriesByName("SeriesWaterPoint"); _seriesUpperPressure = this.chartControl1.GetSeriesByName("SeriesUpperPressure"); _seriesLowerPressure = this.chartControl1.GetSeriesByName("SeriesLowerPressure"); _seriesAveragePressure = this.chartControl1.GetSeriesByName("SeriesAveragePressure"); _constantLineDynamicPressure = _xyDiagram.AxisY.ConstantLines.GetElementByName("ConstantLineDynamicPressure") as ConstantLine; _constantLineStaticPressure = _xyDiagram.AxisY.ConstantLines.GetElementByName("ConstantLineStaticPressure") as ConstantLine; _seriesWaterPoint.LegendTextPattern = "用水点"; _seriesUpperPressure.LegendTextPattern = "压力-上限"; _seriesLowerPressure.LegendTextPattern = "压力-下限"; _seriesAveragePressure.LegendTextPattern = "压力-平均"; _seriesWaterPoint.CrosshairEnabled = DevExpress.Utils.DefaultBoolean.False; _seriesUpperPressure.CrosshairEnabled = DevExpress.Utils.DefaultBoolean.True; _seriesLowerPressure.CrosshairEnabled = DevExpress.Utils.DefaultBoolean.True; _seriesAveragePressure.CrosshairEnabled = DevExpress.Utils.DefaultBoolean.True; this.chartControl1.CrosshairOptions.ShowOnlyInFocusedPane = false; this.chartControl1.CrosshairOptions.GroupHeaderPattern = "{A:N2}"; this.chartControl1.CrosshairOptions.GroupHeaderTextOptions.EnableAntialiasing = DevExpress.Utils.DefaultBoolean.True; _seriesUpperPressure.CrosshairLabelPattern = "{V:N2}"; _seriesLowerPressure.CrosshairLabelPattern = "{V:N2}"; _seriesAveragePressure.CrosshairLabelPattern = "{V:N2}"; _constantLineDynamicPressure.ShowInLegend = false; _constantLineStaticPressure.ShowInLegend = false; _constantLineDynamicPressure.Title.Alignment = ConstantLineTitleAlignment.Far; _constantLineStaticPressure.Title.Alignment = ConstantLineTitleAlignment.Far; _constantLineDynamicPressure.LineStyle.Thickness = 2; _constantLineStaticPressure.LineStyle.Thickness = 2; _xyDiagram.EnableAxisXZooming = true; _xyDiagram.EnableAxisYZooming = true; _xyDiagram.EnableAxisXScrolling = true; _xyDiagram.EnableAxisYScrolling = true; base.InitialDataSource(); var allFacilityList = await BLLFactory.Instance.GetAll(); this.facilityTreeListCtrl1.SetBindingData(allFacilityList); overlay.Close(); } //选择设施 private void FacilityTreeListCtrl1_SelectFacilityEvent(FacilityVmo obj) { InitialData(obj); } /// /// 初始化数据 /// public void InitialData(FacilityVmo obj) { if (obj == null) { _facility = null; return; } _facility = obj; } private void barBtnCalc_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e) { if (_facility == null) { TipFormHelper.ShowWarn("请选择设施!"); return; } //嘉定 ////var file_path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "1843913603388936192.inp"); //var minDemand = 0; // 最小总需水量(m³/h) //var maxDemand = 43; // 最大总需水量(m³/h) //var reservoirElevation = -4; //水池标高 //var calcCount = 1000; // 计算次数 //var staticPressure = 22.5; //静压 //var requiredEndPressure = 18; //静压 var file_path = _facility.ModelPath; var minDemand = 0; // 最小总需水量(m³/h) var maxDemand = _facility.MaxWaterDemand??45; // 最大总需水量(m³/h) var reservoirElevation = -4; //水池标高 var calcCount = 1000; // 计算次数 var staticPressure = 22.5; //静压 var requiredEndPressure = _facility.TerminalPressure??15; //静压 CalcSystemCurve(file_path, minDemand, maxDemand, reservoirElevation, staticPressure, requiredEndPressure, calcCount); } private void CalcSystemCurve( string inpFilePath, double minDemand, double maxDemand, double reservoirElevation, double staticPressure, double requiredEndPressure, int calcCount) { var pressure = 0d; var nodeConfig = new List(); var calcResultList = new List(calcCount); using (var helper = new Yw.Epanet.InteropXHelper()) { var code = helper.Open(inpFilePath, "", ""); CheckCode(code); code = helper.GetCount(Yw.Epanet.eCountType.Node, out int totalNodeCount); CheckCode(code); var rand = new Random(); for (int nodeIndex = 1; nodeIndex <= totalNodeCount; nodeIndex++) { helper.GetNodeType(nodeIndex, out Yw.Epanet.eNodeType nodeType); if (nodeType == Yw.Epanet.eNodeType.Junction) { code = helper.GetNodeId(nodeIndex, out string id); if (!id.Contains("M")) { continue; } helper.GetNodeValue(nodeIndex, Yw.Epanet.eNodeProperty.Elevation, out double elevation); nodeConfig.Add(new WaterDistributionCalculator.NodeConfig() { NodeIndex = nodeIndex, Elevation = elevation, MinFlow = 0, MaxFlow = rand.NextDouble() * 5 }); } else if (nodeType == Yw.Epanet.eNodeType.Reservoir) { code = helper.SetNodeValue(nodeIndex, Yw.Epanet.eNodeProperty.Elevation, reservoirElevation); CheckCode(code); } } var userNodeCount = nodeConfig.Count; var calculator = new WaterDistributionCalculator(nodeConfig, calcCount, minDemand, maxDemand); var allDemands = calculator.CalculateDistributions(); code = helper.OpenH(); CheckCode(code); for (int count = 0; count < allDemands.Count; count++) { var calcNodelDict = new Dictionary(userNodeCount); var calcEndPressure = double.MaxValue; code = helper.InitH(false); CheckCode(code); var demands = allDemands[count]; foreach (var node in nodeConfig) { var userNodeIndex = node.NodeIndex; double demand = demands.NodeFlows[userNodeIndex]; code = helper.SetNodeValue(userNodeIndex, Yw.Epanet.eNodeProperty.BaseDemand, demand); CheckCode(code); calcNodelDict[userNodeIndex] = new CalcNodeViewModel() { NodeIndex = userNodeIndex, Flow = demand }; } code = helper.RunH(out long t); CheckCode(code); foreach (var node in nodeConfig) { var userNodeIndex = node.NodeIndex; pressure = 0; code = helper.GetNodeValue(userNodeIndex, Yw.Epanet.eNodeProperty.Pressure, out pressure); CheckCode(code); var endPressure = pressure + node.Elevation; calcNodelDict[userNodeIndex].Pressure = endPressure; if (calcEndPressure > endPressure) { calcEndPressure = endPressure; } if (Math.Abs(calcEndPressure) > 100) { code = helper.GetNodeId(userNodeIndex, out string id); CheckCode(code); var b = id; } } code = helper.NextH(out long tstep); CheckCode(code); var result = new CalcResultViewModel(); result.TotalFlow = demands.TotalDemand; result.EndPressure = Math.Abs(calcEndPressure); result.CalcNodeList = calcNodelDict.Values.ToList(); calcResultList.Add(result); } code = helper.Close(); CheckCode(code); } this.chartControl1.BeginInit(); _curveUpperPressure = null; _curveLowerPressure = null; _curveAveragePressure = null; _waterPointList = null; _seriesWaterPoint.Points.Clear(); _seriesUpperPressure.Points.Clear(); _seriesLowerPressure.Points.Clear(); _seriesAveragePressure.Points.Clear(); _constantLineDynamicPressure.Visible = false; _constantLineStaticPressure.Visible = false; if (calcResultList.Any()) { _waterPointList = calcResultList.Select(x => new PointViewModel(x.TotalFlow, x.EndPressure + staticPressure + requiredEndPressure)).ToList(); //_waterPointList = calcResultList.Select(x => new PointViewModel(x.TotalFlow, x.EndPressure + staticPressure)).ToList(); var waterPointGroup = _waterPointList.GroupBy(x => Math.Round(x.X, 1)); var upperPressurePtList = new List(); var lowerPressurePtList = new List(); var averagePressurePtList = new List(); foreach (var item in waterPointGroup) { var x = item.Key; var yList = item.Select(x => x.Y).OrderBy(x => x).ToList(); var yUpper = CalculatePercentile(yList, 0.95); var yLower = CalculatePercentile(yList, 0.05); var yNumList = yList.Where(x => x >= yLower && x <= yUpper); if (yNumList==null||!yNumList.Any()) { continue; } var yAverage = yNumList.Where(x => x >= yLower && x <= yUpper).Average(); upperPressurePtList.Add(new Yw.Geometry.Point2d(x, yUpper)); lowerPressurePtList.Add(new Yw.Geometry.Point2d(x, yLower)); averagePressurePtList.Add(new Yw.Geometry.Point2d(x, yAverage)); } if (averagePressurePtList.Count >4) { _curveUpperPressure = new Yw.Ahart.CubicCurve(upperPressurePtList); _curveLowerPressure = new Yw.Ahart.CubicCurve(lowerPressurePtList); _curveAveragePressure = new Yw.Ahart.CubicCurve(averagePressurePtList); var upperPressureCurvePtList = _curveUpperPressure.GetPointList(100); var lowerPressureCurvePtList = _curveLowerPressure.GetPointList(100); var averagePressureCurvePtList = _curveAveragePressure.GetPointList(100); foreach (var item in _waterPointList) { _seriesWaterPoint.Points.Add(new SeriesPoint(item.X, item.Y)); } foreach (var item in upperPressureCurvePtList) { _seriesUpperPressure.Points.Add(new SeriesPoint(item.X, item.Y)); } foreach (var item in lowerPressureCurvePtList) { _seriesLowerPressure.Points.Add(new SeriesPoint(item.X, item.Y)); } foreach (var item in averagePressureCurvePtList) { _seriesAveragePressure.Points.Add(new SeriesPoint(item.X, item.Y)); } var minX = _waterPointList.Min(x => x.X); var maxX = _waterPointList.Max(x => x.X); var minY = _waterPointList.Min(x => x.Y); var maxY = _waterPointList.Max(x => x.Y); } //var minDynamicPressure = minY; //_constantLineDynamicPressure.AxisValue = minDynamicPressure; //_constantLineDynamicPressure.Title.Text = $"最低供水压力:{minDynamicPressure:N2}m"; //_constantLineDynamicPressure.Visible = true; //var totalStaticPressure = staticPressure + Math.Abs(reservoirElevation); //_constantLineStaticPressure.AxisValue = totalStaticPressure; //_constantLineStaticPressure.Title.Text = $"最高楼层高度:{totalStaticPressure:N2}m"; //_constantLineStaticPressure.Visible = true; //minY = Math.Min(staticPressure, minY); //maxY = Math.Max(staticPressure, maxY); //minY = Math.Min(staticPressure, minY); //maxY = Math.Max(staticPressure, maxY); //maxY = Math.Floor(maxY); //maxX = Math.Floor(maxX); //_xyDiagram.AxisX.WholeRange.Auto = true; //_xyDiagram.AxisX.VisualRange.Auto = true; //_xyDiagram.AxisX.WholeRange.SideMarginsValue = 0; //_xyDiagram.AxisX.VisualRange.SideMarginsValue = 0; //_xyDiagram.AxisX.NumericScaleOptions.AutoGrid = true; //_xyDiagram.AxisX.WholeRange.SetMinMaxValues(minX, 50); //_xyDiagram.AxisX.VisualRange.SetMinMaxValues(minX, 50); //_xyDiagram.AxisY.WholeRange.Auto = true; //_xyDiagram.AxisY.VisualRange.Auto = true; //_xyDiagram.AxisY.NumericScaleOptions.AutoGrid = true; //_xyDiagram.AxisY.WholeRange.SideMarginsValue = 0; //_xyDiagram.AxisY.VisualRange.SideMarginsValue = 0; //_xyDiagram.AxisY.WholeRange.SetMinMaxValues(minY, 70); //_xyDiagram.AxisY.VisualRange.SetMinMaxValues(minY, 70); } this.chartControl1.EndInit(); } private double CalculatePercentile(List sortedData, double percentile) { int n = sortedData.Count; double index = (n - 1) * percentile; int lower = (int)Math.Floor(index); int upper = (int)Math.Ceiling(index); double fraction = index - lower; return sortedData[lower] * (1 - fraction) + sortedData[upper] * fraction; } private void CheckCode(Yw.Epanet.eErrorCode code) { if (code != Yw.Epanet.eErrorCode.OK) { if ((int)code>100) { var msg = code.GetDisplayText(); throw new Exception(msg); } } } } }