数据预处理基础:坐标系统介绍与实战

做定位漂移处理,第一步不是调算法,而是搞清楚你的坐标从哪来、要到哪去。我见过太多人上来就怼卡尔曼滤波,结果发现数据源用的GCJ02,地图却是BD09——那漂移能解决才怪。

这一章,咱们就把坐标系统这个地基打牢。

1. 三大坐标系统:WGS84、GCJ02、BD09

说白了,这三个坐标系统就是不同机构对地球的「画法」不同。我刚开始做定位时,以为经纬度就是经纬度,后来被坑惨了。

WGS84(世界大地测量系统)

这是GPS卫星直接输出的坐标。全球通用,精度最高。你手机里的GPS芯片,原始数据就是WGS84。嗯,这里要注意:WGS84在国内地图上直接显示,位置会偏移几百米——这是法律规定的。

GCJ02(国测局坐标)

也叫「火星坐标」。中国官方对WGS84做了非线性加偏处理。高德、腾讯、Google中国地图用的都是它。我在项目中遇到过,用WGS84数据直接在高德地图上打点,结果点跑到了河对岸——当时还以为是GPS坏了。

BD09(百度坐标)

百度在GCJ02基础上又加了一层偏转。说白了就是「火星坐标再加密」。百度地图、百度API都用这个。你想想看,如果数据源是BD09,你用高德地图展示,不转换的话,误差能到几百米。

核心结论: 三个坐标之间,WGS84→GCJ02→BD09是单向加密过程。反过来解密,需要特殊算法。

2. 坐标转换实战

转换算法网上有很多,但质量参差不齐。我个人习惯用这套经过验证的代码,在多个项目中跑过,精度在1米以内。

import math

def wgs84_to_gcj02(lat, lng):
    """WGS84转GCJ02"""
    a = 6378245.0
    ee = 0.00669342162296594323
    
    def transform_lat(x, y):
        ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * math.sqrt(abs(x))
        ret += (20.0 * math.sin(6.0 * x * math.pi) + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0
        ret += (20.0 * math.sin(y * math.pi) + 40.0 * math.sin(y / 3.0 * math.pi)) * 2.0 / 3.0
        ret += (160.0 * math.sin(y / 12.0 * math.pi) + 320.0 * math.sin(y * math.pi / 30.0)) * 2.0 / 3.0
        return ret
    
    def transform_lng(x, y):
        ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * math.sqrt(abs(x))
        ret += (20.0 * math.sin(6.0 * x * math.pi) + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0
        ret += (20.0 * math.sin(x * math.pi) + 40.0 * math.sin(x / 3.0 * math.pi)) * 2.0 / 3.0
        ret += (150.0 * math.sin(x / 12.0 * math.pi) + 300.0 * math.sin(x / 30.0 * math.pi)) * 2.0 / 3.0
        return ret
    
    dlat = transform_lat(lng - 105.0, lat - 35.0)
    dlng = transform_lng(lng - 105.0, lat - 35.0)
    radlat = lat / 180.0 * math.pi
    magic = math.sin(radlat)
    magic = 1 - ee * magic * magic
    sqrtmagic = math.sqrt(magic)
    dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * math.pi)
    dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * math.pi)
    return lat + dlat, lng + dlng

def gcj02_to_bd09(lat, lng):
    """GCJ02转BD09"""
    x = lng
    y = lat
    z = math.sqrt(x * x + y * y) + 0.00002 * math.sin(y * math.pi)
    theta = math.atan2(y, x) + 0.000003 * math.cos(x * math.pi)
    return z * math.sin(theta) + 0.006, z * math.cos(theta) + 0.0065

# 使用示例
wgs_lat, wgs_lng = 39.9042, 116.4074  # 天安门GPS坐标
gcj_lat, gcj_lng = wgs84_to_gcj02(wgs_lat, wgs_lng)
bd_lat, bd_lng = gcj02_to_bd09(gcj_lat, gcj_lng)
print(f"WGS84: {wgs_lat:.6f}, {wgs_lng:.6f}")
print(f"GCJ02: {gcj_lat:.6f}, {gcj_lng:.6f}")
print(f"BD09:  {bd_lat:.6f}, {bd_lng:.6f}")
我的经验: 转换时注意精度控制。有些库用float32,转换后误差会累积到5米以上。建议全程用float64。

3. 数据清洗与异常值剔除

坐标转换完了,下一步就是清洗。原始定位数据里什么妖魔鬼怪都有。我曾经处理过一批数据,里面居然有经纬度是(0,0)的——那是GPS没锁定时的默认值。

常见的异常类型

  • 零值/空值: 经纬度为0或NULL
  • 超出范围: 纬度不在[-90, 90],经度不在[-180, 180]
  • 跳点: 相邻两点距离超过合理阈值(比如1秒内移动了500米)
  • 静止漂移: 设备静止时,坐标却在几十米范围内跳动

清洗策略

我一般分三步走:

  1. 基础过滤: 直接剔除空值和越界点
  2. 速度过滤: 计算相邻点速度,超过阈值(比如120km/h)就标记为异常
  3. 中值滤波: 对连续漂移点做滑动窗口平滑
import numpy as np
from geopy.distance import geodesic

def clean_gps_data(points, max_speed=33.3):
    """
    清洗GPS轨迹数据
    points: [(lat, lng, timestamp), ...]
    max_speed: 最大允许速度,默认33.3m/s(约120km/h)
    """
    cleaned = []
    prev = None
    
    for lat, lng, ts in points:
        # 基础过滤
        if lat == 0 or lng == 0:
            continue
        if not (-90 <= lat <= 90) or not (-180 <= lng <= 180):
            continue
        
        # 速度过滤
        if prev is not None:
            dist = geodesic((prev[0], prev[1]), (lat, lng)).meters
            time_diff = (ts - prev[2]).total_seconds()
            if time_diff > 0:
                speed = dist / time_diff
                if speed > max_speed:
                    continue  # 跳点,剔除
        
        cleaned.append((lat, lng, ts))
        prev = (lat, lng, ts)
    
    return cleaned

# 中值滤波平滑
def median_filter(trajectory, window_size=5):
    """对轨迹做中值滤波,消除小幅度漂移"""
    lats = [p[0] for p in trajectory]
    lngs = [p[1] for p in trajectory]
    
    filtered_lats = []
    filtered_lngs = []
    
    half = window_size // 2
    for i in range(len(trajectory)):
        start = max(0, i - half)
        end = min(len(trajectory), i + half + 1)
        filtered_lats.append(np.median(lats[start:end]))
        filtered_lngs.append(np.median(lngs[start:end]))
    
    return list(zip(filtered_lats, filtered_lngs, [p[2] for p in trajectory]))
避坑指南: 我曾经在速度过滤时用了欧氏距离,结果在赤道附近误差不大,到了高纬度地区偏差巨大。一定要用测地线距离(geodesic),别偷懒。

4. 知识体系总览

这一章的内容,我用一张图帮你理清脉络:

数据预处理知识体系 坐标系统 • WGS84(GPS原始) • GCJ02(火星坐标) • BD09(百度坐标) 三者之间单向加密 转换需特殊算法 坐标转换 • WGS84 → GCJ02 • GCJ02 → BD09 • 反向解密(需特殊处理) 精度控制在1米以内 建议使用float64 数据清洗 • 零值剔除 • 越界过滤 • 速度过滤 • 中值滤波 完整处理流程 原始GPS数据 → 坐标统一 → 异常剔除 → 平滑滤波 → 可用轨迹 坐标系统是基础,转换是桥梁,清洗是保障 WGS84是源头 转换要双向考虑 清洗要分层处理

这张图把咱们这一章的核心逻辑串起来了。你想想看,从GPS芯片拿到WGS84数据,到最终在应用层展示,中间要经过坐标转换和数据清洗两道关卡。任何一个环节出问题,后面的定位算法都是白搭。

总结一下: 坐标系统是定位数据的「语言」,数据清洗是「翻译」和「纠错」。这两步做好了,后面的漂移处理才能事半功倍。

嗯,这一章的内容就到这。代码可以直接拿去用,但记得根据你的实际数据调整阈值参数。每个项目的场景不一样,我给的只是通用方案。

可可精品资料库