# gps_map **Repository Path**: official-account_1/gps_map ## Basic Information - **Project Name**: gps_map - **Description**: 显示瓦片地图 包含坐标转换,验证,手机app,以及瓦片下载软件 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-12-12 - **Last Updated**: 2025-12-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # gps_map #### 介绍 显示瓦片地图 包含坐标转换,验证,手机app,以及瓦片下载软件 # STM32F40 上使用 GPS 经纬度实现 WGS84 转 GCJ02 及墨卡托投影瓦片坐标计算文档 ## 1. 概述 本文档旨在详细说明如何在 **STM32F40** 微控制器上,利用 GPS 获取的原始 WGS84 坐标,经过 **WGS84 → GCJ02(高德/腾讯地图)坐标系转换**,再通过 **墨卡托投影** 计算出对应的地图瓦片坐标(x, y, z),最终生成可访问的高德地图瓦片 URL。整个流程适用于嵌入式设备实现本地地图显示或与高德地图服务对接。 --- ## 2. 系统架构与数据流 ``` GPS模块 (输出WGS84) ↓ STM32F40 MCU (接收并处理) ↓ WGS84 → GCJ02 坐标转换 ↓ GCJ02 → 墨卡托投影 (Web Mercator) ↓ 墨卡托坐标 → 瓦片坐标 (x, y, z) ↓ 生成高德地图瓦片URL ↓ 串口调试输出 / 图像渲染 ``` --- ## 3. 关键概念解析 ### 3.1 坐标系说明 | 坐标系 | 说明 | |--------|------| | **WGS84** | 国际通用地理坐标系,GPS 默认输出格式。 | | **GCJ02** | 中国国家测绘局制定的加密坐标系,也称“火星坐标”。高德、腾讯地图使用此坐标系。 | | **BD09** | 百度地图使用的坐标系,在 GCJ02 基础上进一步偏移。 | | **CGCS2000** | 中国新一代大地坐标系,用于官方测绘。 | > ⚠️ 注意:在中国大陆地区,直接使用 WGS84 坐标在地图上会存在明显偏移,必须转换为 GCJ02 才能正确匹配地图。 --- ### 3.2 墨卡托投影与瓦片系统 - **Web Mercator 投影**:将地球球面投影到平面,常用于在线地图(如 Google Maps、高德、腾讯)。 - **瓦片地图(Tile Map)**:地图被划分为多个层级(zoom level),每个层级由若干 256×256 像素的瓦片组成。 - **瓦片坐标 (x, y, z)**: - `z`:缩放级别(0~20+) - `x`, `y`:该层级下的横向和纵向索引 --- ## 4. 实际案例分析 ### 4.1 原始 GPS 数据 从图中可见,GPS 模块获取的原始坐标为: ``` WGS84: 114.426835, 30.504060 ``` 即:东经 114.426835°,北纬 30.504060°,位于武汉市光谷区域。 --- ### 4.2 WGS84 → GCJ02 转换 通过坐标转换工具(如图1所示),将 WGS84 转换为 GCJ02: ``` WGS84: 114.426835, 30.504060 → GCJ02: 114.432347, 30.501740 ``` > ✅ 此转换是关键步骤,确保后续地图定位准确。 --- ### 4.3 墨卡托投影与瓦片坐标计算 #### 4.3.1 墨卡托投影公式(简化版) ```c double x = lon * 20037508.34 / 180; double y = log(tan((90 + lat) * PI / 360)) / (PI / 180); y = y * 20037508.34 / 180; ``` 其中: - `lon` 和 `lat` 是 GCJ02 的经纬度(单位:度) - 输出 `(x, y)` 为 Web Mercator 平面坐标(米级) #### 4.3.2 瓦片坐标计算 ```c int z = 18; // 缩放级别 int tile_x = (int)((x + 20037508.34) / (20037508.34 / (1 << z))); int tile_y = (int)((20037508.34 - y) / (20037508.34 / (1 << z))); ``` > 注:`20037508.34` 是 Web Mercator 的最大坐标值(约等于赤道周长的一半)。 --- ### 4.4 计算结果(来自串口调试) 根据图5中的调试信息: ``` WGS84 原始经纬度 -> 纬度: 30.504060, 经度: 114.426835 转换后经纬度 -> 纬度: 30.501740, 经度: 114.432347 对应瓦片坐标 -> x = 214399, y = 107731 高德地图瓦片URL -> http://webrd01.is.autonavi.com/appmaptile?x=214399&y=107731&z=18&lang=zh_cn&scl=1&style=8 ``` ✅ 验证无误! --- ## 5. STM32F40 实现代码示例(C语言) ```c #include // 定义常用常数 #define PI 3.14159265358979323846 #define EARTH_RADIUS 6378137.0 #define MAX_LATITUDE 85.05112877 #define MAX_LONGITUDE 180.0 // WGS84 转 GCJ02(简化算法,适用于中国大陆) void wgs84_to_gcj02(double lng, double lat, double *out_lng, double *out_lat) { if (lng < 73.0 || lng > 135.0 || lat < 18.0 || lat > 53.5) { *out_lng = lng; *out_lat = lat; return; } double dLat = transform_lat(lng - 105.0, lat - 35.0); double dLng = transform_lon(lng - 105.0, lat - 35.0); double radLat = lat / 180.0 * PI; double magic = sin(radLat); magic = 1 - 0.00669342162296594323 * magic * magic; double sqrtMagic = sqrt(magic); dLat = (dLat * 180.0) / ((EARTH_RADIUS * 2.0 * PI / 360.0) * sqrtMagic); dLng = (dLng * 180.0) / (EARTH_RADIUS * 2.0 * PI / 360.0); *out_lat = lat + dLat; *out_lng = lng + dLng; } // 辅助函数:变换纬度 double transform_lat(double x, double y) { double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.28 * sqrt(abs(x)); ret += 0.8 * sin(2.0 * x * PI) + 0.3 * cos(2.0 * y * PI); ret += 0.1 * sin(x * PI) + 0.1 * cos(y * PI); return ret; } // 辅助函数:变换经度 double transform_lon(double x, double y) { double ret = 300.0 - x / 6.0 - y / 120.0; ret += 1.5 * sin(2.0 * x * PI) + 0.8 * sin(2.0 * y * PI); ret += 0.2 * sin(x * PI) + 0.2 * cos(y * PI); return ret; } // GCJ02 → 墨卡托投影 void gcj02_to_mercator(double lng, double lat, double *x, double *y) { double maxLon = 180.0; double maxLat = 85.05112877; // 限制范围 lng = fmax(-maxLon, fmin(maxLon, lng)); lat = fmax(-maxLat, fmin(maxLat, lat)); *x = lng * 20037508.34 / 180.0; *y = log(tan((90.0 + lat) * PI / 360.0)) / (PI / 180.0); *y = *y * 20037508.34 / 180.0; } // 墨卡托 → 瓦片坐标 void mercator_to_tile(double x, double y, int zoom, int *tile_x, int *tile_y) { double resolution = 20037508.34 / (1 << zoom); *tile_x = (int)((x + 20037508.34) / resolution); *tile_y = (int)((20037508.34 - y) / resolution); } // 生成高德地图瓦片URL char* generate_amap_tile_url(int x, int y, int z) { static char url[256]; snprintf(url, sizeof(url), "http://webrd01.is.autonavi.com/appmaptile?x=%d&y=%d&z=%d&lang=zh_cn&scl=1&style=8", x, y, z); return url; } ``` --- ## 6. 地图验证与可视化 ### 6.1 高德地图瓦片加载 - 使用上述计算出的 `x=214399`, `y=107731`, `z=18` - 访问 URL:`http://webrd01.is.autonavi.com/appmaptile?x=214399&y=107731&z=18&lang=zh_cn&scl=1&style=8` - 结果如图3所示,成功加载武汉某区域地图瓦片。 ### 6.2 对比验证 - 图2:高德地图 App 显示位置(P点) - 图4:高德地图下载器预览,红色框内为相同区域 - 图7:地图下载器中标注了 `gcj02` 坐标类型,确认使用的是加密坐标系 ✅ 全部一致,验证流程正确。 --- ## 7. 应用场景建议 | 场景 | 说明 | |------|------| | **车载导航** | 在 STM32 上实时计算当前位置瓦片,加载地图 | | **无人机定位** | 将 GPS 坐标转换后用于地面站地图叠加 | | **智能硬件地图展示** | 如智能手表、物联网终端显示地图 | | **离线地图构建** | 预先下载指定区域瓦片,存储于 SD 卡 | --- ## 8. 注意事项 1. **GCJ02 转换精度**:实际应用中建议使用更精确的算法(如公开的“火星坐标”偏移模型)。 2. **网络访问权限**:高德地图瓦片需联网访问,注意防火墙设置。 3. **版权问题**:不得用于商业用途或大规模爬取地图数据。 4. **性能优化**:STM32F40 性能有限,建议对三角函数进行查表优化。 5. **内存管理**:字符串拼接时注意栈空间大小。 --- ## 9. 总结 本项目实现了在 **STM32F40** 上完成从 GPS 接收 WGS84 坐标 → 转换为 GCJ02 → 墨卡托投影 → 计算瓦片坐标 → 生成高德地图瓦片 URL 的完整流程。结合串口调试和地图验证,已成功实现精准地图定位与瓦片加载。 > 🌍 未来可扩展至支持百度 BD09、自定义地图样式、离线缓存等高级功能。 --- **附录:参考资源** - [高德地图 API 文档](https://lbs.amap.com/api/) - [坐标转换工具](https://www.mapsoft.cn/tools/coordinate.html) - [Web Mercator 投影原理](https://en.wikipedia.org/wiki/Web_Mercator_projection) --- **作者:Qwen** **日期:2025年4月5日** 主要代码 ``` #include #include #define ABS(x) (((x)>0)?(x):-(x)) static const double pi = 3.14159265358979324; static const double a = 6378245.0; static const double ee = 0.00669342162296594323; static const double MATH_PI = 3.1415926535897932384626433832795; static double transformLat(double x, double y) { double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(ABS(x)); ret += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0; ret += (20.0 * sin(y * pi) + 40.0 * sin(y / 3.0 * pi)) * 2.0 / 3.0; ret += (160.0 * sin(y / 12.0 * pi) + 320 * sin(y * pi / 30.0)) * 2.0 / 3.0; return ret; } static double transformLon(double x, double y) { double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(ABS(x)); ret += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0; ret += (20.0 * sin(x * pi) + 40.0 * sin(x / 3.0 * pi)) * 2.0 / 3.0; ret += (150.0 * sin(x / 12.0 * pi) + 300.0 * sin(x / 30.0 * pi)) * 2.0 / 3.0; return ret; } /** * 将WGS84坐标系的经纬度转换为火星坐标系(GCJ-02)的经纬度 * * @param wgLat WGS84坐标系的纬度值 * @param wgLon WGS84坐标系的经度值 * @param mgLat 指向存储转换后火星坐标系纬度值的指针 * @param mgLon 指向存储转换后火星坐标系经度值的指针 */ void GPS_Transform(double wgLat, double wgLon, double* mgLat, double* mgLon) { // 计算经纬度偏移量,以(105, 35)为参考点进行变换 double dLat = transformLat(wgLon - 105.0, wgLat - 35.0); double dLon = transformLon(wgLon - 105.0, wgLat - 35.0); // 将纬度转换为弧度制,用于后续三角函数计算 double radLat = wgLat / 180.0 * pi; double magic = sin(radLat); // 计算椭球体参数修正值 magic = 1 - ee * magic * magic; double sqrtMagic = sqrt(magic); // 根据椭球体参数调整偏移量 dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi); dLon = (dLon * 180.0) / (a / sqrtMagic * cos(radLat) * pi); // 将偏移量加到原始坐标上,得到最终的火星坐标 *mgLat = wgLat + dLat; *mgLon = wgLon + dLon; }; uint32_t MapSize(int levelOfDetail) { return (uint32_t)256 << levelOfDetail; } void LatLongToPixelXY(double latitude, double longitude, int levelOfDetail, int* pixelX, int* pixelY) { // 直接使用输入的经纬度,不进行裁剪(取消 Clip) double x = (longitude + 180.0) / 360.0; // 将纬度转为弧度并计算墨卡托投影的 y double latRad = latitude * MATH_PI / 180.0; double sinLatitude = sin(latRad); // 注意:当 latitude 接近 ±90° 时,sinLatitude → ±1,可能导致 log(∞) 或 NaN // 此版本不处理该情况(按需求保留原始行为) double y = 0.5 - log((1.0 + sinLatitude) / (1.0 - sinLatitude)) / (4.0 * MATH_PI); // 获取地图尺寸 uint32_t mapSize = MapSize(levelOfDetail); // 计算像素坐标(四舍五入) *pixelX = ((int)(x * mapSize + 0.5)) / 256; *pixelY = ((int)(y * mapSize + 0.5)) / 256; } ```