.net解析雷达拼图3.0组合反射率产品,并按经纬度裁剪绘制组合反射率图

1.组合反射率产品文件名称:

Z_RADA_C_BABJ_2025XXXXXXXXXX_P_DOR_ACHN_CREF_2025XXXX_XXXXXX.bin2

2.文件格式

拼图的雷达文件分为两个部分:文件头 + 数据块,文件头包括一下数据

文件固定标识 (4字节)

文件格式版本代码 (4字节)

文件字节数 (4字节)

拼图产品编号 (2字节)

坐标系类型 (2字节)

变量名 (8字节)

描述信息 (64字节)

块位置 (4字节)

块长度 (4字节)

时区 (4字节)

年份 (2字节)

月份 (2字节)

日期 (2字节)

小时 (2字节)

分钟 (2字节)

秒钟 (2字节)

观测秒数 (4字节)

观测日期 (2字节)

生成日期 (2字节)

生成秒数 (4字节)

最小纬度 (4字节,除以1000)12.2

最小经度 (4字节,除以1000)73

最大纬度 (4字节,除以1000)54.2

最大经度 (4字节,除以1000)135

中心点X (4字节,除以1000)104

中心点Y (4字节,除以1000)33.2

列数 (4字节)6200

行数 (4字节)4200

X方向分辨率 (4字节,除以10000)0.01

Y方向分辨率 (4字节,除以10000)0.01

高度 (2字节)

压缩标志 (2字节)1

雷达数量 (4字节)239

解压后字节数 (4字节)

数据缩放因子 (2字节)

未使用字段 (2字节)

区域ID (8字节)

单位 (8字节)

保留字段 (8字节)

数据块中在后续解压和解析中使用的数据有时间(年月日时分秒),经、纬度方向网格数,最大最小经纬度,数据缩放因子,创建对象将这些数据存储起来

3.完整代码

cs 复制代码
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.Intrinsics.X86;
using System.Text;
using ICSharpCode.SharpZipLib.BZip2;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public class RadarDataParser
{
    // 定义反射率等级 (dBZ)
    private static readonly double[] Levels = { 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70 };

    // 定义与每个反射率等级对应的颜色 (RGBA)
    private static readonly Color[] Colors =
    {
        Color.FromArgb(0, 0, 239),     // 0-5 dBZ
        Color.FromArgb(65, 157, 241),  // 5-10 dBZ
        Color.FromArgb(100, 231, 235), // 10-15 dBZ
        Color.FromArgb(109, 250, 61),  // 15-20 dBZ
        Color.FromArgb(0, 216, 0),     // 20-25 dBZ
        Color.FromArgb(1, 144, 0),     // 25-30 dBZ
        Color.FromArgb(255, 255, 0),   // 30-35 dBZ
        Color.FromArgb(231, 192, 0),   // 35-40 dBZ
        Color.FromArgb(255, 144, 0),   // 40-45 dBZ
        Color.FromArgb(255, 0, 0),     // 45-50 dBZ
        Color.FromArgb(214, 0, 0),     // 50-55 dBZ
        Color.FromArgb(192, 0, 0),     // 55-60 dBZ
        Color.FromArgb(255, 0, 240),   // 60-65 dBZ
        Color.FromArgb(150, 0, 180),   // 65-70 dBZ
        Color.FromArgb(173, 144, 240)  // >70 dBZ
    };

    /// <summary>
    /// 解析雷达数据文件并生成雷达图像
    /// </summary>
    /// <param name="filePath">雷达数据文件路径</param>
    public static void Parse(string filePath, double lonMin, double lonMax, double latMin, double latMax, string savePath)
    {
        byte[] fileBytes = File.ReadAllBytes(filePath);

        using (var stream = new MemoryStream(fileBytes))
        using (var reader = new BinaryReader(stream))
        {
            var header = ReadHeader(reader);
            stream.Seek(256, SeekOrigin.Begin);

            byte[] compressedData = reader.ReadBytes(fileBytes.Length - 256);

            byte[] decompressedData = DecompressBZip2(compressedData);

            if (decompressedData == null)
            {
                Console.WriteLine("数据解压失败");
                return;
            }

            short[] data = new short[decompressedData.Length / 2];
            Buffer.BlockCopy(decompressedData, 0, data, 0, decompressedData.Length);

            double[,] array2D = new double[header.nY, header.nX];

            for (int i = 0; i < header.nY; i++)
            {
                for (int j = 0; j < header.nX; j++)
                {
                    short value = data[i * header.nX + j];

                    double scaledValue = value / (double)header.scale;

                    array2D[i, j] = (scaledValue >= 0 && scaledValue <= 75) ? scaledValue : double.NaN;
                }
            }

            // 生成经纬度网格
            double[] lon = new double[header.nX];
            double[] lat = new double[header.nY];

            // 经度网格 (从左到右)
            for (int i = 0; i < header.nX; i++)
            {
                lon[i] = header.lonMin + i * (header.lonMax - header.lonMin) / (header.nX - 1);
            }

            // 纬度网格 (从下到上)
            for (int i = 0; i < header.nY; i++)
            {
                lat[i] = header.latMin + i * (header.latMax - header.latMin) / (header.nY - 1);
            }

            // 裁剪裁剪地区数据
            var croppedData = CropDataForHenan(array2D, lon, lat, lonMin, lonMax, latMin, latMax);
            double[,] croppedArray = croppedData.Data;
            int cropWidth = croppedData.Width;
            int cropHeight = croppedData.Height;
            int startX = croppedData.StartX;
            int startY = croppedData.StartY;

            DateTime dt = new DateTime(header.yr, header.mon, header.day, header.hr, header.min, 0).AddHours(8);//雷达日期
            String ttpp = $"{Path.DirectorySeparatorChar}{dt.ToString("yyyy")}{Path.DirectorySeparatorChar}{dt.ToString("MM")}{Path.DirectorySeparatorChar}";
            //  创建位图并绘制裁剪雷达数据
            using (Bitmap bitmap = new Bitmap(cropWidth, cropHeight, PixelFormat.Format32bppArgb))
            {
                // 遍历每个像素点
                for (int y = 0; y < cropHeight; y++)
                {
                    for (int x = 0; x < cropWidth; x++)
                    {
                        // 获取数据值
                        double value = croppedArray[y, x];

                        // 根据值获取对应颜色
                        Color color = GetColorForValue(value);

                        // 设置像素颜色
                        bitmap.SetPixel(x, y, color);
                    }
                }

                //保存为透明背景的PNG图像
                string descPath = string.Format(@"{0}\{1}", savePath, ttpp);
                if (!Directory.Exists(descPath))
                {
                    Directory.CreateDirectory(descPath);
                }
                string outputPath = Path.Combine(descPath, dt.ToString("yyyyMMddHHmm") + ".png");
                
                if (File.Exists(outputPath)) { File.Delete(outputPath); }
                bitmap.Save(outputPath, ImageFormat.Png);
                Console.WriteLine($"裁剪雷达图像已成功保存为: {outputPath}");
               
            }
            try
            {
                File.Delete(filePath);
            }
            catch (Exception ex)
            {

            }
        }

    }
    /// <summary>
    /// 裁剪裁剪地区数据
    /// </summary>
    private static CroppedData CropDataForHenan(double[,] fullData, double[] lon, double[] lat, double lonMin, double lonMax, double latMin, double latMax)
    {
        int xStart = -1, xEnd = -1;
        for (int i = 0; i < lon.Length; i++)
        {
            if (lon[i] >= lonMin && xStart == -1) xStart = i;
            if (lon[i] <= lonMax) xEnd = i;
            else break;
        }

        int yStart = -1, yEnd = -1;
        for (int i = 0; i < lat.Length; i++)
        {
            if (lat[i] >= latMin && yStart == -1) yStart = i;
            if (lat[i] <= latMax) yEnd = i;
            else break;
        }

        if (xStart == -1 || xEnd == -1 || yStart == -1 || yEnd == -1)
        {
            return new CroppedData
            {
                Data = fullData,
                Width = fullData.GetLength(1),
                Height = fullData.GetLength(0),
                StartX = 0,
                StartY = 0
            };
        }

        int width = xEnd - xStart + 1;
        int height = yEnd - yStart + 1;

        double[,] cropped = new double[height, width];

        for (int y = yStart; y <= yEnd; y++)
        {
            for (int x = xStart; x <= xEnd; x++)
            {
                cropped[y - yStart, x - xStart] = fullData[y, x];
            }
        }

        return new CroppedData
        {
            Data = cropped,
            Width = width,
            Height = height,
            StartX = xStart,
            StartY = yStart
        };
    }
    // <summary>
    /// 裁剪数据结果容器
    /// </summary>
    private class CroppedData
    {
        public double[,] Data { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
        public int StartX { get; set; }
        public int StartY { get; set; }
    }
    /// <summary>
    /// 根据反射率值获取对应颜色
    /// </summary>
    /// <param name="value">反射率值 (dBZ)</param>
    /// <returns>对应的颜色,无效值为透明</returns>
    private static Color GetColorForValue(double value)
    {
        if (double.IsNaN(value))
            return Color.Transparent;

        for (int i = 0; i < Levels.Length - 1; i++)
        {
            if (value >= Levels[i] && value < Levels[i + 1])
            {
                return Colors[i];
            }
        }

        if (value >= Levels[Levels.Length - 1])
        {
            return Colors[Colors.Length - 1];
        }

        return Color.Transparent;
    }

    /// <summary>
    /// 解压BZip2格式数据
    /// </summary>
    /// <param name="data">压缩的字节数组</param>
    /// <returns>解压后的字节数组,失败返回null</returns>
    private static byte[] DecompressBZip2(byte[] data)
    {
        try
        {
            using (var input = new MemoryStream(data))
            using (var output = new MemoryStream())
            {
                BZip2.Decompress(input, output, false);
                return output.ToArray();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"BZip2解压错误: {ex.Message}");
            return null;
        }
    }

    /// <summary>
    /// 读取雷达数据文件头
    /// </summary>
    /// <param name="reader">二进制读取器</param>
    /// <returns>包含头信息的RadarHeader对象</returns>
    private static RadarHeader ReadHeader(BinaryReader reader)
    {
        var header = new RadarHeader();

        reader.BaseStream.Seek(0, SeekOrigin.Begin);

        string label = ReadString(reader, 4, "gb2312");
        string version = ReadString(reader, 4, "gb2312");
        int file_bytes = reader.ReadInt32();
        short mosaic_id = reader.ReadInt16();
        short coordinate = reader.ReadInt16();
        string varname = ReadString(reader, 8, "gb2312");
        string description = ReadString(reader, 64, "gb2312");
        int BlockPos = reader.ReadInt32();
        int BlockLen = reader.ReadInt32();
        int TimeZone = reader.ReadInt32();
        header.yr = reader.ReadInt16();
        header.mon = reader.ReadInt16();
        header.day = reader.ReadInt16();
        header.hr = reader.ReadInt16();
        header.min = reader.ReadInt16();
        short sec = reader.ReadInt16();
        int ObsSeconds = reader.ReadInt32();
        short ObsDates = reader.ReadInt16();
        short GenDates = reader.ReadInt16();
        int GenSeconds = reader.ReadInt32();
        header.latMin = reader.ReadInt32() / 1000.0;
        header.lonMin = reader.ReadInt32() / 1000.0; 
        header.latMax = reader.ReadInt32() / 1000.0; 
        header.lonMax = reader.ReadInt32() / 1000.0;
        double cx = reader.ReadInt32() / 1000.0;
        double cy = reader.ReadInt32() / 1000.0;
        header.nX = reader.ReadInt32();
        header.nY = reader.ReadInt32();
        double dx = reader.ReadInt32() / 10000.0;
        double dy = reader.ReadInt32() / 10000.0; 
        short height = reader.ReadInt16();
        short compress = reader.ReadInt16();
        int num_of_radars = reader.ReadInt32();
        int UnZipBytes = reader.ReadInt32();
        header.scale = reader.ReadInt16();
        short unUsed = reader.ReadInt16(); 
        string RgnID = ReadString(reader, 8, "gb2312");
        string units = ReadString(reader, 8, "gb2312");
        string reserved = ReadString(reader, 8, "gb2312");

        // 验证读取位置是否正确 (总头长度应为256字节,有部分保留字节)
        if (reader.BaseStream.Position != 256)
        {
            // 强制跳转到256字节位置
            reader.BaseStream.Seek(256, SeekOrigin.Begin);
        }

        return header;
    }
    /// <summary>
    /// 读取固定长度的GB2312编码字符串 
    /// </summary>
    /// <param name="reader">二进制读取器</param>
    /// <param name="length">字符串长度</param>
    /// <param name="encoding">编码格式</param>
    /// <returns>处理后的字符串</returns>
    private static string ReadString(BinaryReader reader, int length, string encoding = "gb2312")
    {
        byte[] bytes = reader.ReadBytes(length);
        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
        Encoding enc = Encoding.GetEncoding(encoding);
        string str = enc.GetString(bytes);

        return str.Replace("\0", "").Replace(" ", "");
    }
    /// <summary>
    /// 雷达文件头信息结构 (包含所有必要字段)
    /// </summary>
    private class RadarHeader
    {
        public short yr;         // 年份
        public short mon;        // 月份
        public short day;        // 日期
        public short hr;         //小时
        public short min;       //分钟
        public int nX;           // 经度方向网格数
        public int nY;           // 纬度方向网格数
        public double latMin;   // 最小纬度
        public double latMax;   // 最大纬度
        public double lonMin;   // 最小经度
        public double lonMax;   // 最大经度
        public short scale;     // 数据缩放因子

        public override string ToString()
        {
            return $"日期: {yr}-{mon}-{day}, 网格: {nX}x{nY}, 纬度范围: [{latMin},{latMax}], 经度范围: [{lonMin},{lonMax}], 缩放因子: {scale}";
        }
    }
}

class Program
{
    static JObject valueconfig;
    static void Main(string[] args)
    {
        try
        {
            System.AppContext.SetSwitch("System.Drawing.EnableUnixSupport", true);
            string pathh = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;

            using (System.IO.StreamReader file = new System.IO.StreamReader(AppDomain.CurrentDomain.BaseDirectory + "config.json", Encoding.UTF8))
            {
                valueconfig = JsonConvert.DeserializeObject<JObject>(file.ReadToEnd());
                file.Close();
            }
            Console.WriteLine("雷达数据解析");

            string[] files = Directory.GetFiles(valueconfig["pathfile"].ToString(), "*.*");
            foreach (string file in files)
            {
                if (!File.Exists(file))
                {
                    Console.WriteLine($"文件不存在: {file}");
                    continue;
                }
                double henanLonMin = 109.46, henanLonMax = 124.22, henanLatMin = 27.05, henanLatMax = 38.80;
                try
                {
                    henanLonMin = double.Parse(valueconfig["minlon"].ToString());  // 裁剪最小经度
                    henanLonMax = double.Parse(valueconfig["maxlon"].ToString()); // 裁剪最大经度
                    henanLatMin = double.Parse(valueconfig["minlat"].ToString());  // 裁剪最小纬度
                    henanLatMax = double.Parse(valueconfig["maxlat"].ToString());   // 裁剪最大纬度
                }
                catch (Exception ex) { }

                Console.WriteLine($"处理裁剪雷达数据 (范围: 经度[{henanLonMin}-{henanLonMax}], 纬度[{henanLatMin}-{henanLatMax}])");

                // 处理雷达数据并生成裁剪地区图像
                RadarDataParser.Parse(file, henanLonMin, henanLonMax, henanLatMin, henanLatMax, valueconfig["savePath"].ToString());
            }
        }catch(Exception ex) { Console.WriteLine(ex.Message); }
        

    }
}

配置文件格式

XML 复制代码
{
  "source": "天擎雷达拼图解析",
  "pathfile": "E:\\雷达文件",
  "savePath": "E:\\雷达解析",
  "minlon": 109.46, //最小73
  "maxlon": 124.22, //最大135
  "minlat": 27.05, //最小12.2
  "maxlat": 38.80 //最大54.2
}

代码中用到了包Newtonsoft.Json处理json文件(主要是配置文件),SharpZipLib解压文件,Syatem.Drawing.Common用来画图

4.整体思路:

读取文件头信息

读取压缩数据部分

解压BZip2数据

创建二维数组存储处理后的数据

生成经纬度网格

裁剪裁剪地区数据(可以不用裁剪,直接用全国数据)

根据色标卡生成对应的色板图

删除文件(每6分钟一组数据,后期会比较占存储空间)

5.最后生成的雷达图

相关推荐
geovindu12 分钟前
CSharp: Task Scheduler
开发语言·后端·c#·.net·.netcore
海盗12342 小时前
微软技术日报 2026-09-10:Project Zenith 开发者设备亮相,.NET 11 RC1 可投产
microsoft·r语言·.net
唐青枫17 小时前
C#.NET PInvoke 深入解析:封送、内存布局与原生互操作实战
c#·.net
yywww19 小时前
GenHTTP on .NET 11 登顶HTTP Arena 多个榜单(这是何方神圣)
.net
咕白m6251 天前
C# 如何限制 PDF 复制、打印权限?
c#·.net
慧都小妮子1 天前
GcExcel 服务端 Excel 组件:无需安装 Office 的 Java/.NET 报表方案
java·.net·excel·gcexcel·服务端excel组件
我是唐青枫2 天前
C#.NET StructureMap 从依赖注入到项目实战
开发语言·c#·.net
Lost of 程序猿2 天前
C# 12 / .NET 9 新特性机制深度:编译器替你写了什么,哪些糖吃下去会出事
windows·c#·.net
唐青枫3 天前
C#.NET StructureMap 从依赖注入到项目实战
c#·.net
智码看视界3 天前
.NET 10 推理大模型TensorSharp 3.3.0 部署实测:纯.NET推理引擎反超llama.cpp 1.5倍,DFlash2提速62%
c#·.net·llama.cpp·.net 10·tensorsharp·本地大模型推理·开源推理引擎