基于C#的测井孔隙度计算小软件

仅作自己学习适用


0 前言

      在最近学习C#时,想要做一个和自己方向相关的东西巩固一下自己学的东西,上一篇博客写了一下动物识别系统,这是C#的基础语法。本篇想要巩固一下学习的winform课程,因次想到了这个孔隙度的计算软件。

1 技术

      项目量很少,在熟练背景知识和winform基础的情况下只需几个小时就能完成,完成这个项目一共分为三层,表示层,业务逻辑层和数据层;需要完成的工作有:

  1. UI的涉及
  2. 自定义曲线的存储结构
  3. 标准txt数据格式的读和写
  4. 参数控制
  5. 绘图控制
  6. 孔隙度计算模型

2 软件截图

在这里插入图片描述
在这里插入图片描述

3 代码结构

页面布局用到的控件有:

labeltextboxbuttongroupBoxNumericalUpDown
ComboBoxChartTableLayoutPanelDataGridView

      在完成对页面布局的涉及后,首先要完成的是对文件的读写操作,以方便后续的处理,因为读取的是曲线信息,因次要专门写一个曲线的类用于定义曲线的数据结构:

LogDataIO.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PORCalcApp
{
    /// <summary>
    /// 实现文件的读写
    /// </summary>
    public class LogDataIO
    {
        /// <summary>
        /// 读取曲线
        /// </summary>
        /// <param name="FileName">文件路径</param>
        /// <returns></returns>
        public static List<LogCurve> ReadCurveList(string FileName)
        {
            // 曲线列表
            List<LogCurve> curveList = new List<LogCurve>();

            // 读取文件为一行一行的字符串数组(这种方式就不适用于特别大的文件,因为是一次性读取所有文件到一个数组里)
            string[] strArray = System.IO.File.ReadAllLines(FileName);// 里边是按行按行存放的一组信息,就是txt里边的一行

            // step1:判断所读取文件里有多少根曲线(nameStr里存放的就是曲线名字,多了一个深度,第一列都是深度信息)
            // 可能不同文件里的拆分方式不同,把所有的情况要考虑到,第二个参数的意思是拆分的时候去除空
            string[] nameStr = strArray[0].Split(" ,\t;".ToCharArray(),StringSplitOptions.RemoveEmptyEntries);

            // step2:获取起始深度,终止深度,采样间隔
            float topdep = float.Parse(strArray[1].Split(" ,\t;".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).First()); // 第二行的第一个数值为起始深度,仍然采用这种拆分方式
            float botdep = float.Parse(strArray.Last().Split(" ,\t;".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).First()); // 最后一行的第一个数值为终止深度,仍然采用这种拆分方式
            float spacing = (botdep - topdep)/(strArray.Length-1-1); //采样间隔 = (终止深度-起始深度)/(文件总行数-1-1);文件第一行是那些曲线名称,不是数据

            // step3:曲线信息设置(从1开始循环,因为nameStr的第一个元素是深度,不是曲线)
            for (int n = 1; n < nameStr.Length; n++)
            {
                LogCurve curve = new LogCurve();
                curve.Name = nameStr[n];
                curve.TopDep = topdep;
                curve.BotDep = botdep;
                curve.Spacing = spacing;
                curve.Value = new float[strArray.Length-1]; //先给曲线的那组值初始化个数组用于存放
                curveList.Add(curve);
            }
            
            // step4:获取曲线值(仍然是跳过第一行)
            for (int n = 1; n < strArray.Length; n++)
            {
                // 取出当前行,先将当前行拆开
                string[] strTempList = strArray[n].Split("\t, ;".ToCharArray(),StringSplitOptions.RemoveEmptyEntries);
                for (int m = 0; m < curveList.Count; m++)
                {
                    curveList[m].Value[n - 1] = float.Parse(strTempList[m + 1]);
                }
            }

            return curveList;
        }

        /// <summary>
        /// 将曲线写入到文件中
        /// </summary>
        /// <param name="FileName">目标文件名</param>
        /// <param name="curveList">输出曲线列表</param>
        public static void WriteCurveList(string FileName,List<LogCurve> curveList)
        {
            StringBuilder sb = new StringBuilder();

            // 曲线深度
            sb.Append("DEPTH".PadRight(16, ' ')); // 控制格式,向右填充空格到16个字符
            // 将所有曲线名称添加进去
            for (int i = 0; i < curveList.Count; i++)
            {
                sb.Append(curveList[i].Name.PadRight(16, ' '));
            }
            sb.AppendLine(); // 第一行写完后换行

            // 添加曲线值
            for (int n = 0; n < curveList.First().Value.Length; n++)
            {
                float dep = curveList.First().TopDep + n * curveList.First().Spacing; // 计算当前深度
                sb.Append(dep.ToString().PadRight(16, ' '));
                for(int m = 0;m<curveList.Count; m++) // 添加曲线值
                {
                    sb.Append(curveList[m].Value[n].ToString("0.0000").PadRight(16, ' '));
                }
                sb.AppendLine();
            }

            // 写到文件中去 
            System.IO.File.WriteAllText(FileName, sb.ToString());
        }
    }

    /// <summary>
    /// 自定义的曲线的数据类型
    /// </summary>
    public class LogCurve
    {
        // 曲线名字
        public string Name { get; set; }
        // 单位
        public string Unit { get; set; } = "";
        // 起始深度
        public float TopDep { get; set; } = 0;
        // 终止深度
        public float BotDep { get; set; } = 10000;
        // 采样间隔
        public float Spacing { get; set; } = 0.1f;
        // 数据值
        public float[] Value { get; set; }
    }
}

第二步,完成对模型类的定义
Pars.cs

using System;

namespace PORCalcApp
{
    /// <summary>
    /// 模型类,参数相关信息
    /// </summary>
    public class Pars
    {
        // GR最小值
        public float GRmin { get; set; } = 50.0f;
        // GR最大值
        public float GRmax { get; set; } = 250.0f;
        // 地层参数
        public float C { get; set; } = 4.0f;
        // 密度最小值
        public float DENmin { get; set; } = 1.2f;
        // 密度最大值
        public float DENmax { get; set; } = 2.9f;
        // 泥质
        public float DENclay { get; set; } = 1.7f;

        /// <summary>
        /// 计算泥质含量
        /// </summary>
        /// <param name="gr">伽马</param>
        /// <param name="grmin">纯砂岩GR</param>
        /// <param name="grmax">纯泥岩GR</param>
        /// <param name="C">地层参数</param>
        /// <returns></returns>
        public static float CalVSH(float gr,float grmin,float grmax,float C)
        {
            float vsh = 0; //泥质含量
            float igr = (gr - grmin) / (grmax - grmin);   //gr体积因子
            vsh = (float)((Math.Pow(2, C * igr) - 1) / ((Math.Pow(2, C) - 1)));
            return vsh;
        }

        /// <summary>
        /// 计算孔隙度
        /// </summary>
        /// <param name="den">密度</param>
        /// <param name="denmin">流体密度</param>
        /// <param name="denmax">骨架密度</param>
        /// <param name="dencaly">泥质密度</param>
        /// <param name="vsh">泥质含量</param>
        /// <returns></returns>
        public static float CalPOR(float den,float denmin,float denmax,float dencaly,float vsh)
        {
            float por = 0;
            por = (den-denmax) /(denmin-denmax) - vsh*(dencaly - denmax)/(denmin-denmax);
            return por;
        }
    }
}

第三步完成业务逻辑:
Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;

namespace PORCalcApp
{
    public partial class Form1 : Form
    {
        private List<LogCurve> logCurves;
        private LogCurve POR;
        private Pars p;
        public Form1()
        {
            InitializeComponent();
            chart_GR.Series.Clear();
            chart_DEN.Series.Clear();
            chart_POR.Series.Clear();

            InivializeControls();
        }
        /// <summary>
        /// 给参数赋初值(初始化参数信息)
        /// </summary>
        private void InivializeControls()
        {
            p = new Pars();
            numericUpDown_GRmax.Value = (decimal)p.GRmax;
            numericUpDown_GRmin.Value = (decimal)p.GRmin;
            numericUpDown_DENmax.Value = (decimal)p.DENmax;
            numericUpDown_DENmin.Value = (decimal)p.DENmin;
            numericUpDown_DENvsh.Value = (decimal)p.DENclay;
            numericUpDown_RePar.Value = (decimal)p.C;
        }

        // 数据加载按钮
        private void button_select_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = "TEXT|*.txt"; //过滤一下可以选择的文件类型
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                textBox_file.Text = ofd.FileName;
                logCurves = LogDataIO.ReadCurveList(ofd.FileName);
                // 将曲线信息显示在表格里
                Load2DataGridView();
                // 数据加载完成后立即就有GR和DEN的曲线信息,将他们显示出来
                Plot();
            }
        }

        private void Plot()
        {
             绘制GR
            // step1 先清空数据,放置产生累计效果
            chart_GR.Series.Clear();
            // step2 新建Serise存放数据
            Series sgr = new Series();
            sgr.ChartType = SeriesChartType.FastLine; // 指定类型为线性的
            LogCurve lc = logCurves[comboBox_GR.SelectedIndex]; // 选中要画的曲线
            // step3 绘图,将所有点绘制出来
            for (int i = 0;i<lc.Value.Length;i++)
            {
                // 横坐标是曲线值,纵坐标是深度信息
                sgr.Points.AddXY(lc.Value[i], lc.TopDep + i * lc.Spacing);
            }
            // step4 添加到图中去
            chart_GR.Series.Add(sgr);
            // step5 优化显示,否则图很丑
            // 设置x轴的最大最小值(÷10再×10)
            chart_GR.ChartAreas.First().AxisX.Minimum = ((int)lc.Value.Min() / 10) * 10;
            chart_GR.ChartAreas.First().AxisX.Maximum = ((int)lc.Value.Max() / 10) * 10;
            // 设置y轴的最大最小值
            chart_GR.ChartAreas.First().AxisY.Minimum = ((int)lc.TopDep / 10) * 10;
            chart_GR.ChartAreas.First().AxisY.Maximum = ((int)lc.BotDep / 10) * 10;
            // 设置图例
            sgr.Name = lc.Name;



             绘制DEN
            chart_DEN.Series.Clear();
            Series sden = new Series();
            sden.ChartType = SeriesChartType.FastLine;
            lc = logCurves[comboBox_DEN.SelectedIndex]; // lc = logCurves[comboBox_DEN.SelectedIndex];
            for (int i = 0; i < lc.Value.Length; i++)
            {
                sden.Points.AddXY(lc.Value[i], lc.TopDep + i * lc.Spacing);
            }
            chart_DEN.Series.Add(sden);
            chart_DEN.ChartAreas.First().AxisX.Minimum = (float)lc.Value.Min();
            chart_DEN.ChartAreas.First().AxisX.Maximum = (float)lc.Value.Max();
            chart_DEN.ChartAreas.First().AxisY.Minimum = ((int)lc.TopDep / 10) * 10;
            chart_DEN.ChartAreas.First().AxisY.Maximum = ((int)lc.BotDep / 10) * 10;
            sden.Name = lc.Name;

             绘制POR
            if (POR == null) return;
            
            Series spor = new Series();
            spor.ChartType = SeriesChartType.FastLine;
            lc = POR;
            for (int i = 0; i < lc.Value.Length; i++)
            {
                spor.Points.AddXY(lc.Value[i], lc.TopDep + i * lc.Spacing);
            }
            chart_POR.Series.Add(spor);
            chart_POR.ChartAreas.First().AxisX.Minimum = 0;
            chart_POR.ChartAreas.First().AxisX.Maximum = ((int)lc.Value.Max()); //(int)lc.Value.Max()*10.0f + 10) * 10.0f
            chart_POR.ChartAreas.First().AxisY.Minimum = ((int)lc.TopDep / 10) * 10;
            chart_POR.ChartAreas.First().AxisY.Maximum = ((int)lc.BotDep / 10) * 10;
            spor.Name = lc.Name;
        }

        private void Load2DataGridView()
        {
            // 简单方法,这样就不能修改了
              // dataGridView_curvelist.DataSource = logCurves;
            // 常规方法
            dataGridView_curvelist.Rows.Clear(); //先将里边的内容清空
            comboBox_DEN.Items.Clear();
            comboBox_GR.Items.Clear();
            for (int n = 0; n < logCurves.Count; n++)
            {
                // 添加一行
                dataGridView_curvelist.Rows.Add();
                // 给每行添加序号
                dataGridView_curvelist.Rows[n].HeaderCell.Value = (n+1).ToString();
                // 添加列信息
                dataGridView_curvelist.Rows[n].Cells[0].Value = logCurves[n].Name; // 名字
                dataGridView_curvelist.Rows[n].Cells[1].Value = logCurves[n].Unit; // 单位
                dataGridView_curvelist.Rows[n].Cells[2].Value = logCurves[n].TopDep;  // 起始深度
                dataGridView_curvelist.Rows[n].Cells[3].Value = logCurves[n].BotDep;  // 终止深度
                dataGridView_curvelist.Rows[n].Cells[4].Value = logCurves[n].Spacing;  // 采样间隔
                comboBox_DEN.Items.Add(logCurves[n].Name); // 给两个下拉框添加上数据
                comboBox_GR.Items.Add(logCurves[n].Name);
            }

            // 给DEN下拉框默认值
            for (int n = 0; n < logCurves.Count; n++)
            {
                if (comboBox_DEN.Items[n].Equals("DEN"))
                {
                    comboBox_DEN.SelectedIndex = n;
                    break;
                }
            }
            // 如果没有找到目标DEN曲线
            if (comboBox_DEN.SelectedIndex <= 0)
            {
                comboBox_DEN.SelectedIndex = 0;
            }
            // 给目标GR曲线默认值
            for (int n = 0; n < logCurves.Count; n++)
            {
                if (comboBox_GR.Items[n].Equals("GR"))
                {
                    comboBox_GR.SelectedIndex = n;
                    break;
                }
            }
            // 如果没有找到目标GR曲线
            if (comboBox_GR.SelectedIndex <= 0)
            {
                comboBox_GR.SelectedIndex = 0;
            }
        }

        // 计算按钮
        private void button_calc_Click(object sender, EventArgs e)
        {
            // step1 计算之前要把用户设置的参数保存起来
            SavePars();
            // step2 计算模型
            RunMethod();
            // step3 做出图像
            Plot();
        }

        /// <summary>
        /// 计算模型
        /// </summary>
        private void RunMethod()
        {
            POR = new LogCurve();
            POR.Name = "POR";
            POR.Unit = "%";
            POR.TopDep = logCurves.First().TopDep;
            POR.BotDep = logCurves.First().BotDep;
            POR.Spacing = logCurves.First().Spacing;
            POR.Value = new float[logCurves.First().Value.Length];
            for (int n = 0; n < logCurves.First().Value.Length; n++)
            {
                // 取出GR和DEN
                float gr = logCurves[comboBox_GR.SelectedIndex].Value[n];
                float den = logCurves[comboBox_DEN.SelectedIndex].Value[n];
                // 得到泥质含量
                float vsh = Pars.CalVSH(gr,p.GRmin,p.GRmax,p.C);
                // 得到孔隙度
                POR.Value[n] = Pars.CalPOR(den, p.DENmin, p.DENmax, p.DENclay, vsh) * 100;
                
            }
            
        }

        /// <summary>
        /// 保存参数信息 
        /// </summary>
        private void SavePars()
        {
            p.GRmax = (float)numericUpDown_GRmax.Value;
            p.GRmin = (float)numericUpDown_GRmin.Value;
            p.DENmax = (float)numericUpDown_DENmax.Value;
            p.DENmin = (float)numericUpDown_DENmin.Value;
            p.DENclay = (float)numericUpDown_DENvsh.Value;
            p.C = (float)numericUpDown_RePar.Value;
        }

        // 保存按钮
        private void button_save_Click(object sender, EventArgs e)
        {
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.Filter = "TEXT|*.txt";
            if (sfd.ShowDialog() == DialogResult.OK)
            {
                // 将孔隙度加载进来
                logCurves.Add(POR);
                LogDataIO.WriteCurveList(sfd.FileName, logCurves);
                MessageBox.Show("保存成功!");
            }
        }

        // 清空按钮
        private void button1_Click(object sender, EventArgs e)
        {
            chart_POR.Series.Clear();
        }
    }
}

4 总结

      除了没有给出UI的代码,因为都是直接拖拽的,其余部分全部完成,完整的工程文件和打包好的安装程序:POR计算小软件.

这是自己学习时的总结,仅作自己学习使用。

tic: 2023.08.22

5 代码优化

      仔细观察不难发现,在绘制GR,DEN,POR曲线时,很多地方都是重复的,因次可以将重复的部分提取出来,提高代码的复用,这里将三个绘图的地方提取为一个PlotChart()方法:

void PlotChart(Chart chart,LogCurve lc,SeriesChartType sType,double AxisXMin,double AxisXMax)
{
    chart.Series.Clear();
    Series s = new Series();
    s.ChartType = sType; // 指定绘制类型
    for (int i = 0; i < lc.Value.Length; i++)
    {
        double x = lc.Value[i];
        double y = lc.TopDep + i * lc.Spacing;
        s.Points.AddXY(x, y);
    }
    chart.ChartAreas.First().AxisX.Minimum = AxisXMin;
    chart.ChartAreas.First().AxisX.Maximum = AxisXMax; //Math.Round(lc.Value.Max()+2  , 2)

    chart.ChartAreas.First().AxisY.Minimum = lc.TopDep / 10 * 10;
    chart.ChartAreas.First().AxisY.Maximum = lc.BotDep / 10 * 10;
    chart.Series.Add(s); 
}

这样在三个函数那里调用就可以了,如下:

// 绘制GR
LogCurve log = logCurves[comboBox_GR.SelectedIndex];
double AxisXMin = Math.Round(log.Value.Min() - 2, 2);
double AxisXMax = Math.Round(log.Value.Max() + 2, 2);
PlotChart(chart_GR, log, SeriesChartType.Line, AxisXMin, AxisXMax);

// 绘制DEN
log = logCurves[comboBox_DEN.SelectedIndex];
AxisXMin = Math.Round(log.Value.Min()-0.1 , 3);
AxisXMax = Math.Round(log.Value.Max()+0.1 , 3);
PlotChart(chart_DEN, log, SeriesChartType.Line, AxisXMin, AxisXMax);

// 绘制孔隙度
if (POR == null) return;
AxisXMin = 0;
AxisXMax = Math.Round(POR.Value.Max() + 0.1, 3);
PlotChart(chart_POR, POR, SeriesChartType.FastLine, AxisXMin, AxisXMax);

TODO:
这里没有指定对y轴刻度(深度方向)的最大最小值的调整,因为我认为三条曲线的深度信息是相同的,因次没有调整(不知道理解是否正确)。

tic:2023.08.27

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

亲爱的老吉先森

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值