You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# .NET控制台应用:读取车辆位置dat文件失败,无法查找最近车辆

问题解决:二进制文件读取与高效最近车辆位置查找

一、解决二进制文件读取错误

你用StreamReader读取二进制文件是错误的,二进制文件不能按文本编码解析,这就是你看到乱码的原因。必须用BinaryReader按照给定的二进制结构逐字段读取。

正确的文件读取代码

首先定义对应的数据模型:

public class VehiclePosition
{
    public int PositionId { get; set; }
    public string VehicleRegistration { get; set; }
    public float Latitude { get; set; }
    public float Longitude { get; set; }
    public ulong RecordedTimeUTC { get; set; }
}

然后编写读取逻辑:

public static List<VehiclePosition> ReadVehiclePositions(string filePath)
{
    var positions = new List<VehiclePosition>();
    
    using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
    using (var reader = new BinaryReader(stream))
    {
        while (stream.Position < stream.Length)
        {
            // 读取PositionId(Int32,4字节)
            var positionId = reader.ReadInt32();
            
            // 读取null终止的ASCII字符串
            var registrationBytes = new List<byte>();
            byte b;
            while ((b = reader.ReadByte()) != 0)
            {
                registrationBytes.Add(b);
            }
            var registration = Encoding.ASCII.GetString(registrationBytes.ToArray());
            
            // 读取Latitude(Float,4字节)
            var latitude = reader.ReadSingle();
            
            // 读取Longitude(Float,4字节)
            var longitude = reader.ReadSingle();
            
            // 读取RecordedTimeUTC(UInt64,8字节)
            var recordedTime = reader.ReadUInt64();
            
            positions.Add(new VehiclePosition
            {
                PositionId = positionId,
                VehicleRegistration = registration,
                Latitude = latitude,
                Longitude = longitude,
                RecordedTimeUTC = recordedTime
            });
        }
    }
    
    return positions;
}

二、高效最近位置查找方案

基准测试是10次遍历200万条数据,时间复杂度为O(10*N),我们可以用KD-Tree构建空间索引,将查询时间降到O(logN),整体时间复杂度为O(N logN)(构建索引) + O(10 logN)(查询),远快于基准方案。

1. 实现KD-Tree用于二维坐标最近邻查找

public class KdNode
{
    public VehiclePosition Position { get; set; }
    public KdNode Left { get; set; }
    public KdNode Right { get; set; }
    public int Axis { get; set; } // 0=Latitude, 1=Longitude
}

public class KdTree
{
    private KdNode _root;

    public KdTree(List<VehiclePosition> positions)
    {
        _root = BuildTree(positions, 0);
    }

    private KdNode BuildTree(List<VehiclePosition> points, int depth)
    {
        if (points.Count == 0) return null;

        int axis = depth % 2;
        // 按当前轴排序
        var sortedPoints = axis == 0 
            ? points.OrderBy(p => p.Latitude).ToList() 
            : points.OrderBy(p => p.Longitude).ToList();

        int medianIndex = sortedPoints.Count / 2;
        var medianPoint = sortedPoints[medianIndex];

        return new KdNode
        {
            Position = medianPoint,
            Axis = axis,
            Left = BuildTree(sortedPoints.Take(medianIndex).ToList(), depth + 1),
            Right = BuildTree(sortedPoints.Skip(medianIndex + 1).ToList(), depth + 1)
        };
    }

    public VehiclePosition FindNearest(float targetLat, float targetLon)
    {
        var nearest = new KeyValuePair<KdNode, double>();
        SearchNearest(_root, targetLat, targetLon, ref nearest);
        return nearest.Key?.Position;
    }

    private void SearchNearest(KdNode node, float targetLat, float targetLon, ref KeyValuePair<KdNode, double> nearest)
    {
        if (node == null) return;

        double currentDistance = CalculateDistanceSquared(node.Position.Latitude, node.Position.Longitude, targetLat, targetLon);
        
        // 更新最近点
        if (!nearest.Key.HasValue || currentDistance < nearest.Value)
        {
            nearest = new KeyValuePair<KdNode, double>(node, currentDistance);
        }

        // 确定搜索方向
        int axis = node.Axis;
        double targetValue = axis == 0 ? targetLat : targetLon;
        double nodeValue = axis == 0 ? node.Position.Latitude : node.Position.Longitude;

        KdNode firstBranch = targetValue < nodeValue ? node.Left : node.Right;
        KdNode secondBranch = targetValue < nodeValue ? node.Right : node.Left;

        // 先搜索当前分支
        SearchNearest(firstBranch, targetLat, targetLon, ref nearest);

        // 检查另一分支是否可能有更近的点
        double axisDistanceSquared = Math.Pow(targetValue - nodeValue, 2);
        if (axisDistanceSquared < nearest.Value)
        {
            SearchNearest(secondBranch, targetLat, targetLon, ref nearest);
        }
    }

    // 计算平面距离的平方(避免开根号,提升速度,比较距离时等价)
    private double CalculateDistanceSquared(float lat1, float lon1, float lat2, float lon2)
    {
        double dLat = lat1 - lat2;
        double dLon = lon1 - lon2;
        return dLat * dLat + dLon * dLon;
    }

    // 如果需要精确球面距离,用Haversine公式:
    // private double CalculateHaversineDistance(float lat1, float lon1, float lat2, float lon2)
    // {
    //     const double R = 6371000; // 地球半径,单位米
    //     double dLat = ToRadians(lat2 - lat1);
    //     double dLon = ToRadians(lon2 - lon1);
    //     double a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
    //                Math.Cos(ToRadians(lat1)) * Math.Cos(ToRadians(lat2)) *
    //                Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
    //     double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
    //     return R * c;
    // }
    // private double ToRadians(double degrees) => degrees * Math.PI / 180;
}

2. 完整的主程序逻辑

class Program
{
    static void Main(string[] args)
    {
        string dataFilePath = @"C:\Users\Downloads\VehiclePositions_DataFile\VehiclePositions.dat";
        
        // 1. 读取所有车辆位置
        Console.WriteLine("正在读取数据文件...");
        var allPositions = ReadVehiclePositions(dataFilePath);
        Console.WriteLine($"读取完成,共{allPositions.Count}条记录");

        // 2. 构建KD-Tree索引
        Console.WriteLine("正在构建空间索引...");
        var kdTree = new KdTree(allPositions);
        Console.WriteLine("索引构建完成");

        // 3. 定义需要查询的10个坐标(修正了你代码中Lat/Lon颠倒的问题)
        var targetCoords = new List<(float Latitude, float Longitude)>
        {
            (34.544909f, -102.100843f),
            (32.345544f, -99.123124f),
            (33.234235f, -100.214124f),
            (35.195739f, -95.348899f),
            (31.895839f, -97.789573f),
            (32.895839f, -101.789573f),
            (34.115839f, -100.225732f),
            (32.335839f, -99.992232f),
            (33.535339f, -94.792232f),
            (32.234235f, -100.22222f)
        };

        // 4. 执行查询并输出结果
        Console.WriteLine("\n开始查询最近车辆位置:");
        for (int i = 0; i < targetCoords.Count; i++)
        {
            var coord = targetCoords[i];
            var nearest = kdTree.FindNearest(coord.Latitude, coord.Longitude);
            
            Console.WriteLine($"坐标{i+1} ({coord.Latitude}, {coord.Longitude}) 的最近车辆:");
            Console.WriteLine($"  PositionId: {nearest.PositionId}");
            Console.WriteLine($"  车牌号: {nearest.VehicleRegistration}");
            Console.WriteLine($"  坐标: ({nearest.Latitude}, {nearest.Longitude})");
            Console.WriteLine($"  记录时间(UTC秒): {nearest.RecordedTimeUTC}\n");
        }
    }

    // 这里粘贴之前的ReadVehiclePositions方法
    public static List<VehiclePosition> ReadVehiclePositions(string filePath)
    {
        // ... 代码同上 ...
    }
}

三、关键优化点说明

  1. 二进制文件读取:用BinaryReader严格按照字段类型和顺序读取,避免文本解析的乱码问题。
  2. 空间索引(KD-Tree):仅需一次构建索引,后续每个查询的时间复杂度为O(logN),相比基准的O(N)查询,200万条数据下速度提升非常明显。
  3. 距离计算优化:使用距离平方代替实际距离,避免开根号运算,减少计算开销。如果需要精确的球面距离,可以替换为Haversine公式。
  4. 数据模型修正:修正了你代码中Latitude和Longitude颠倒的错误,确保坐标匹配。

内容的提问来源于stack exchange,提问作者Aubz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 05:48:06