ningshuxia
2025-03-27 0f5eaa62a6ce8c24c056e2d690bc2e6a9958637e
WinFrmUI/PBS.WinFrmUI.Hydro/05-system-curve/00-core/SystemCurvePage.cs
@@ -1,150 +1,26 @@
using DevExpress.Mvvm.Native;
using DevExpress.XtraCharts;
namespace PBS.WinFrmUI.Hydro
namespace PBS.WinFrmUI.Hydro
{
    public partial class SystemCurvePage : DocumentPage
    {
        public SystemCurvePage()
        {
            InitializeComponent();
            this.facilityTreeListCtrl1.SelectFacilityEvent += FacilityTreeListCtrl1_SelectFacilityEvent;
            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<CalcNodeViewModel> 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<PointViewModel> _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;
        }
         
        private FacilityVmo _facility = null;
        /// <summary>
        /// 初始化数据源
        /// </summary>
        public override async void InitialDataSource()
        {
            base.InitialDataSource(); 
            var allFacilityList = await BLLFactory<BLL.Facility>.Instance.GetAll();
            this.facilityTreeListCtrl1.SetBindingData(allFacilityList);
            overlay.Close();
            this.facilityTreeListCtrl1.SetBindingData(allFacilityList);
        }
@@ -153,21 +29,15 @@
        {
            InitialData(obj);
        }
        /// <summary>
        /// 初始化数据
        /// </summary> 
        public void InitialData(FacilityVmo obj)
        {
            if (obj == null)
            {
                _facility = null;
                return;
            }
            _facility = obj;
            this.facilityPropertyCtrl1.SetBindingData(obj);
            this.systemCurveChartCtrl1.Clear();
        }
@@ -179,52 +49,58 @@
                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 = 10;           // 计算次数
            var staticPressure = 22.5; //静压
            var requiredEndPressure = _facility.TerminalPressure??15; //静压
            CalcSystemCurve(file_path, minDemand, maxDemand, reservoirElevation, staticPressure, requiredEndPressure, calcCount);
            var maxDemand = _facility.MaxWaterDemand??45;  // 最大总需水量(m³/h)
            var calcCount = 20000;           // 计算次数
            var maxHeight = 22.5; //顶楼标高
            var requiredEndPressure = _facility.TerminalPressure ?? 15; //静压
            CalcSystemCurve(file_path, minDemand, maxDemand, maxHeight, requiredEndPressure, calcCount);
        }
        /// <summary>
        /// 计算系统曲线
        /// </summary>
        /// <param name="inpFilePath"></param>
        /// <param name="minDemand"></param>
        /// <param name="maxDemand"></param>
        /// <param name="reservoirElevation"></param>
        /// <param name="maxHeight"></param>
        /// <param name="requiredEndPressure"></param>
        /// <param name="calcCount"></param>
        private void CalcSystemCurve(
            string inpFilePath,
            double minDemand,
            double maxDemand,
            double reservoirElevation,
            double staticPressure,
            //double reservoirElevation,
            double maxHeight,
            double requiredEndPressure,
            int calcCount)
        {
            
            var pressure = 0d;
            var nodeConfig = new List<WaterDistributionCalculator.NodeConfig>();
            var calcResultList = new List<CalcResultViewModel>(calcCount);
            var waterPointCalcList = new List<WaterPointCalcViewModel>(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()
                        {
@@ -233,12 +109,7 @@
                            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;
@@ -246,12 +117,14 @@
                var allDemands = calculator.CalculateDistributions();
                code = helper.OpenH();
                CheckCode(code);
                for (int count = 0; count < allDemands.Count; count++)
                {
                    var calcNodelDict = new Dictionary<int, CalcNodeViewModel>(userNodeCount);
                    var calcNodelDict = new Dictionary<int, WaterPointNodeViewModel>(userNodeCount);
                    var calcEndPressure = double.MaxValue;
                    code = helper.InitH(false);
                    CheckCode(code);
                    var demands = allDemands[count];
                    foreach (var node in nodeConfig)
                    {
@@ -260,194 +133,63 @@
                        code = helper.SetNodeValue(userNodeIndex, Yw.Epanet.eNodeProperty.BaseDemand, demand);
                        CheckCode(code);
                        calcNodelDict[userNodeIndex] = new CalcNodeViewModel()
                        calcNodelDict[userNodeIndex] = new WaterPointNodeViewModel()
                        {
                            NodeIndex = userNodeIndex,
                            Index = 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;
                        code = helper.GetNodeValue(userNodeIndex, Yw.Epanet.eNodeProperty.Pressure, out pressure);
                        //自由压力=绝对压力-标高
                        //默认起始点压力为最低水位,所以用 绝对压力 当自由压力 (绝对压力=自由压力+标高)
                        code = helper.GetNodeValue(userNodeIndex, Yw.Epanet.eNodeProperty.Head, out double head);
                        CheckCode(code);
                        var endPressure = pressure + node.Elevation;
                        var endPressure = head;
                        calcNodelDict[userNodeIndex].Pressure = endPressure;
                        if (calcEndPressure > endPressure)
                        {
                            calcEndPressure = endPressure;
                        }
                        if (Math.Abs(calcEndPressure) > 100)
                        {
                            code = helper.GetNodeId(userNodeIndex, out string id);
                            var b = id;
                        }
                    }
                    code = helper.NextH(out long tstep);
                    var result = new CalcResultViewModel();
                    result.TotalFlow = demands.TotalDemand;
                    result.EndPressure = Math.Abs(calcEndPressure);
                    result.CalcNodeList = calcNodelDict.Values.ToList();
                    calcResultList.Add(result);
                    CheckCode(code);
                    var waterPointCalc = new WaterPointCalcViewModel();
                    waterPointCalc.TotalFlow = demands.TotalDemand;
                    waterPointCalc.EndPressure = Math.Abs(calcEndPressure) + maxHeight + requiredEndPressure;
                    waterPointCalc.NodeList = calcNodelDict.Values.ToList();
                    waterPointCalcList.Add(waterPointCalc);
                }
                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<Yw.Geometry.Point2d>();
                var lowerPressurePtList = new List<Yw.Geometry.Point2d>();
                var averagePressurePtList = new List<Yw.Geometry.Point2d>();
                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));
                }
                _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();
            this.systemCurveChartCtrl1.SetBindingData(waterPointCalcList);
        }
        private double CalculatePercentile(List<double> 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)
            {
                var msg = code.GetDisplayText();
                throw new Exception(msg);
                if ((int)code>100)
                {
                    var msg = code.GetDisplayText();
                    throw new Exception(msg);
                }
            }
        }