当前位置: 首页 > news >正文

SIMXXX 在高德地图定位到我的位置

摘要:SIM7600X 4G HAT因其完善的功能以及稳定的性能深受世人的钟爱,但SIM7600X 4G HAT上网是很犀利,可为什么SIM7600定位和我的位置差异这么大(将SIM7600X获取到的坐标系放到百度或者高德地图坐标反查),有几条街的差距呢。下面让我们走进GNSS的大街里走一走,看看是谁用了时空转移:

硬件准备

  • SIM7600X 4G HAT
  • 树莓派4B

硬件连接图

软件准备

  • 高德地图API

原理分析

SIM7600X用到的坐标系是WGS-84坐标系;而我们常用的百度或者高德地图用到的坐标系是经过加密的百度坐标(bd09)和火星坐标系(gcj_02)。因此,如果直接将WGS-84坐标系放到百度或者高德等地图是有很大误差的(放到谷歌地图可以)此外,SIM7600获取到的经纬度是分为单位,需要先转换常用的单位度:

SIMXXX获取经纬度等位置信息

温馨提示:

  • 由于 GPS 室内搜星不稳定,请将模块或者天线放到阳台或窗户旁,或者直接在户外进行实验。
  • 插上GPS天线,并将接收器标签面朝下置于空旷的室外在正常情况下(户外,天气良好,没有大型建筑遮挡)上电需要等待约1分钟才能接收到定位信号;如果天气条件不好,可能需要更长的定位时间,甚至无法定位。
  • 发送以下指令打开GPS:
AT+CGPS=1
  • 用以指令获取经纬度等位置信息:
AT+CGPSINFO
  • 打开NEMA获取详细信息
sudo minicom -D /dev/ttyUSB1

  • 单位转换
ddmm.mm--> dd.mm.ss: 度数不变,分数*100/60 : 如2232.448620--> 22.(32448620*100/60)= 22.54081033
  • 编辑并保存示例程序
    sudo nano SIMXXX.py
from socket import socket import sys import re import pynmea2 import serial import chardet import time import math import json global Latitude global Longitude global line global line1 global ser2 x_pi = 3.14159265358979324 * 3000.0 / 180.0 pi = 3.1415926535897932384626 # π a = 6378245.0 # Semi-major axis ee = 0.00669342162296594323 # Eccentricity squared def _transformlng(longitude, latitude): ret = 300.0 + longitude + 2.0 * latitude + 0.1 * longitude * longitude + \ 0.1 * longitude * latitude + 0.1 * math.sqrt(math.fabs(longitude)) ret += (20.0 * math.sin(6.0 * longitude * pi) + 20.0 * math.sin(2.0 * longitude * pi)) * 2.0 / 3.0 ret += (20.0 * math.sin(longitude * pi) + 40.0 * math.sin(longitude / 3.0 * pi)) * 2.0 / 3.0 ret += (150.0 * math.sin(longitude / 12.0 * pi) + 300.0 * math.sin(longitude / 30.0 * pi)) * 2.0 / 3.0 return ret def _transformlat(longitude, latitude): ret = -100.0 + 2.0 * longitude + 3.0 * latitude + 0.2 * latitude * latitude + \ 0.1 * longitude * latitude + 0.2 * math.sqrt(math.fabs(longitude)) ret += (20.0 * math.sin(6.0 * longitude * pi) + 20.0 * math.sin(2.0 * longitude * pi)) * 2.0 / 3.0 ret += (20.0 * math.sin(latitude * pi) + 40.0 * math.sin(latitude / 3.0 * pi)) * 2.0 / 3.0 ret += (160.0 * math.sin(latitude / 12.0 * pi) + 320 * math.sin(latitude * pi / 30.0)) * 2.0 / 3.0 return ret def setup(): # global response ser2 = serial.Serial("/dev/ttyUSB2",115200) print("ttyUSB2 Open!!!") ser2.write('AT+CGPS=1\r'.encode()) print("AT+CGPS=1") ser2.close() print("ttyUSB2 Close!!!") def loop(): global ser1 ser1 = serial.Serial("/dev/ttyUSB1",115200) print("ttyUSB1 Open!!!") while True: line = str(ser1.readline(),encoding='utf-8') if line.startswith("$GPRMC"): global Longitude global Latitude rmc = pynmea2.parse(line) if re.match("^\d+?\.\d+?$", rmc.lat)is not None: print(rmc) latitude = rmc.latitude longitude= rmc.longitude # ''' SIM820X uses the gcj_02 coordinate system, no coordinate conversion is required dlat = _transformlat(longitude - 105.0, latitude - 35.0) dlng = _transformlng(longitude - 105.0, latitude - 35.0) radlat = latitude / 180.0 * pi magic = math.sin(radlat) magic = 1 - ee * magic * magic sqrtmagic = math.sqrt(magic) dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi) dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi) mglat = latitude + dlat mglng = longitude + dlng # '''Please comment out the conversion part print ("longitude,latitude")#longitude,latitude print (""+str(mglng)+","+str(mglat)+"")#经度,纬度 time.sleep(2) def destroy(): ser1.close() print("ttyUSB1 Close!!!") ser2 = serial.Serial("/dev/ttyUSB2",115200) print("ttyUSB2 Open!!!") ser1.close() print("ttyUSB1 Close!!!") ser2.close() print("ttyUSB2 Close!!!") try: setup() loop() except KeyboardInterrupt: destroy()
  • 运行示例程序后生成的坐标复制到高德地图API
sudo pip3 install pynmea2 sudo pip3 install pynmeagps sudo python3 SIM7600X-GNSS.py

114.0832857092161,22.53842762954979
  • 我的位置

小编在世界贸易贸易广场的南侧窗边(不小心暴露了小编的位置,找到我请您喝茶,哈哈!),定位和我有1-2米的误差,有转换和硬件本身决定的,不同的测试环境和转换算法,可能误差有所差异。

或者可以用这个 UI 界面程序,可以直接跳转

#!/usr/bin/env python3 # -*- coding: utf-8 -*- from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from urllib.parse import urlparse, parse_qs import json import threading import time import glob import re import math import serial HOST = "0.0.0.0" PORT = 8888 BAUD_LIST = [9600, 115200, 460800] ser = None ser_lock = threading.Lock() buf = "" status = { "serial": "Disconnected", "port": "", "baud": "", "fix": "Invalid", "lat_wgs": "", "lon_wgs": "", "lat_gcj": "", "lon_gcj": "", "last_gga": "", "message": "Ready", } PI = math.pi A = 6378245.0 EE = 0.00669342162296594323 GGA_RE = re.compile(r"\$(?:GNGGA|GPGGA),[^$]*?\*[0-9A-Fa-f]{2}") def out_of_china(lat, lon): return not (72.004 <= lon <= 137.8347 and 0.8293 <= lat <= 55.8271) def transform_lat(x, y): ret = -100 + 2*x + 3*y + 0.2*y*y + 0.1*x*y + 0.2*math.sqrt(abs(x)) ret += (20*math.sin(6*x*PI) + 20*math.sin(2*x*PI)) * 2/3 ret += (20*math.sin(y*PI) + 40*math.sin(y/3*PI)) * 2/3 ret += (160*math.sin(y/12*PI) + 320*math.sin(y*PI/30)) * 2/3 return ret def transform_lon(x, y): ret = 300 + x + 2*y + 0.1*x*x + 0.1*x*y + 0.1*math.sqrt(abs(x)) ret += (20*math.sin(6*x*PI) + 20*math.sin(2*x*PI)) * 2/3 ret += (20*math.sin(x*PI) + 40*math.sin(x/3*PI)) * 2/3 ret += (150*math.sin(x/12*PI) + 300*math.sin(x*PI/30)) * 2/3 return ret def wgs84_to_gcj02(lat, lon): if out_of_china(lat, lon): return lat, lon dlat = transform_lat(lon - 105.0, lat - 35.0) dlon = transform_lon(lon - 105.0, lat - 35.0) radlat = lat / 180.0 * PI magic = math.sin(radlat) magic = 1 - EE * magic * magic sqrtmagic = math.sqrt(magic) dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrtmagic) * PI) dlon = (dlon * 180.0) / (A / sqrtmagic * math.cos(radlat) * PI) return lat + dlat, lon + dlon def parse_gga(gga): f = gga.split(",") if len(f) < 7 or not f[2] or not f[4] or f[6] == "0": return None try: lat = int(f[2][:2]) + float(f[2][2:]) / 60.0 lon = int(f[4][:3]) + float(f[4][3:]) / 60.0 if f[3] == "S": lat = -lat if f[5] == "W": lon = -lon return lat, lon, f[6] except: return None def get_ports(): ports = [] for p in ("/dev/ttyUSB*", "/dev/ttyACM*", "/dev/ttyS*", "/dev/ttyAMA*"): ports += glob.glob(p) ports = sorted(set(ports)) return ports or ["/dev/ttyUSB2"] def open_serial(port, baud): global ser, buf with ser_lock: if ser: try: ser.close() except: pass ser = None ser = serial.Serial(port, int(baud), timeout=0.1) buf = "" status["serial"] = "Connected" status["port"] = port status["baud"] = str(baud) status["message"] = "Serial opened" def close_serial(): global ser with ser_lock: if ser: try: ser.close() except: pass ser = None status["serial"] = "Disconnected" status["message"] = "Serial closed" def reader_loop(): global buf while True: try: with ser_lock: s = ser if s is None: time.sleep(0.2) continue data = s.read(512) if not data: time.sleep(0.02) continue buf += data.decode("ascii", errors="ignore") for m in GGA_RE.finditer(buf): gga = m.group() status["last_gga"] = gga r = parse_gga(gga) if not r: continue lat, lon, fix = r lat_gcj, lon_gcj = wgs84_to_gcj02(lat, lon) status["fix"] = fix status["lat_wgs"] = f"{lat:.8f}" status["lon_wgs"] = f"{lon:.8f}" status["lat_gcj"] = f"{lat_gcj:.8f}" status["lon_gcj"] = f"{lon_gcj:.8f}" status["message"] = "Receiving GGA" buf = buf[-2048:] except Exception as e: status["serial"] = "Disconnected" status["message"] = f"Read error: {e}" close_serial() time.sleep(0.5) threading.Thread(target=reader_loop, daemon=True).start() HTML = """<!doctype html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <title>GNSS → AMap</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> body{margin:0;padding:24px;font-family:Arial,"Microsoft YaHei";background:#e9edf3;color:#24364b} .wrap{max-width:1100px;margin:auto} .title{font-size:34px;font-weight:700;margin-bottom:18px} .card{background:#f6f8fb;border-radius:18px;padding:22px;box-shadow:0 2px 12px rgba(0,0,0,.08)} .grid{display:grid;grid-template-columns:1fr 1fr;gap:14px} label{display:block;font-size:14px;font-weight:700;margin-bottom:8px;color:#52657a} select,button,input{width:100%;padding:13px 14px;border-radius:12px;border:1px solid #d7dfe8;background:#fff;font-size:14px;box-sizing:border-box} button{background:#3b6fd8;color:#fff;border:none;cursor:pointer} a.btn{display:inline-block;padding:12px 16px;border-radius:12px;border:1px solid #d7dfe8;background:#fff;color:#24364b;text-decoration:none} .links{display:flex;gap:12px;flex-wrap:wrap;margin-top:14px} pre{background:#fff;border:1px solid #d7dfe8;border-radius:12px;padding:14px;min-height:220px;white-space:pre-wrap;word-break:break-word} @media (max-width:800px){.grid{grid-template-columns:1fr}} </style> </head> <body> <div class="wrap"> <div class="title">GNSS → AMap</div> <div class="card"> <div class="grid"> <div> <label>串口</label> <select id="port"></select> </div> <div> <label>波特率</label> <select id="baud"> <option>9600</option> <option selected>115200</option> <option>460800</option> </select> </div> </div> <div class="links"> <button onclick="openPort()">打开串口</button> <a class="btn" href="/close">关闭串口</a> <a class="btn" id="amap" href="#" target="_blank">打开高德</a> </div> <div style="margin-top:14px"><pre id="s">loading...</pre></div> </div> </div> <script> async function loadPorts(){ const r=await fetch('/api'); const d=await r.json(); const sel=document.getElementById('port'); sel.innerHTML=''; d.ports.forEach(p=>{ const o=document.createElement('option'); o.value=p; o.textContent=p; if(d.port===p) o.selected=true; sel.appendChild(o); }); if(d.baud) document.getElementById('baud').value=d.baud; } async function refresh(){ const r=await fetch('/api'); const d=await r.json(); document.getElementById('s').textContent = 'serial : ' + d.serial + '\\n' + 'port : ' + d.port + '\\n' + 'baud : ' + d.baud + '\\n' + 'fix : ' + d.fix + '\\n' + 'lat_wgs : ' + d.lat_wgs + '\\n' + 'lon_wgs : ' + d.lon_wgs + '\\n' + 'lat_gcj : ' + d.lat_gcj + '\\n' + 'lon_gcj : ' + d.lon_gcj + '\\n' + 'message : ' + d.message + '\\n\\n' + (d.last_gga || ''); document.getElementById('amap').href = d.amap || '#'; } async function openPort(){ const port=document.getElementById('port').value; const baud=document.getElementById('baud').value; location.href='/open?port=' + encodeURIComponent(port) + '&baud=' + encodeURIComponent(baud); } loadPorts(); refresh(); setInterval(refresh,1000); setInterval(loadPorts,5000); </script> </body> </html> """ class Handler(BaseHTTPRequestHandler): def send_text(self, code, text, ctype="text/html; charset=utf-8"): data = text.encode("utf-8") self.send_response(code) self.send_header("Content-Type", ctype) self.send_header("Content-Length", str(len(data))) self.end_headers() self.wfile.write(data) def send_json(self, obj): data = json.dumps(obj, ensure_ascii=False).encode("utf-8") self.send_response(200) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Content-Length", str(len(data))) self.end_headers() self.wfile.write(data) def redirect(self, location="/"): self.send_response(302) self.send_header("Location", location) self.end_headers() def do_GET(self): p = urlparse(self.path) qs = parse_qs(p.query) if p.path == "/": self.send_text(200, HTML) return if p.path == "/api": amap = "" if status["lat_gcj"] and status["lon_gcj"]: amap = ( f"https://ditu.amap.com/regeo?lng={status['lon_gcj']}" f"&lat={status['lat_gcj']}&src=uriapi&innersrc=uriapi" ) self.send_json({**status, "ports": get_ports(), "amap": amap}) return if p.path == "/open": port = qs.get("port", [""])[0] baud = qs.get("baud", ["115200"])[0] try: open_serial(port, baud) except Exception as e: status["serial"] = "Disconnected" status["message"] = f"Open failed: {e}" self.redirect("/") return if p.path == "/close": close_serial() self.redirect("/") return self.send_text(404, "404") def log_message(self, format, *args): pass if __name__ == "__main__": print(f"Server: http://0.0.0.0:{PORT}") ThreadingHTTPServer((HOST, PORT), Handler).serve_forever()

HTML 转换高德工具

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>GPS坐标转换与查看</title> <style> body { font-family: Arial, sans-serif; background-color: #f4f7fc; padding: 20px; } .container { max-width: 600px; margin: 0 auto; background-color: white; border-radius: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); padding: 30px; } h1 { text-align: center; font-size: 28px; margin-bottom: 20px; color: #333; } label { display: block; margin-bottom: 10px; color: #555; font-weight: bold; } input, button { width: 100%; padding: 12px; margin-bottom: 20px; border-radius: 8px; border: 1px solid #dfe3e8; font-size: 16px; box-sizing: border-box; } button { background-color: #4CAF50; color: white; border: none; cursor: pointer; } button:hover { background-color: #45a049; } .results { margin-top: 20px; } .results p { font-size: 16px; color: #555; } .link-btn { display: block; width: 100%; text-align: center; padding: 12px; background-color: #008CBA; color: white; border: none; font-size: 16px; border-radius: 8px; cursor: pointer; margin-top: 10px; } .link-btn:hover { background-color: #007bb5; } </style> <script> // 将经纬度的度分格式转换为十进制 function convertToDecimal(degreesMinutes) { const degrees = Math.floor(degreesMinutes / 100); const minutes = degreesMinutes - degrees * 100; return degrees + (minutes / 60); } // WGS-84 转 GCJ-02 坐标转换算法 const PI = Math.PI; const A = 6378245.0; // WGS-84半长轴 const EE = 0.00669342162296594323; // 偏心率平方 function out_of_china(lat, lon) { return !(lon >= 72.004 && lon <= 137.8347 && lat >= 0.8293 && lat <= 55.8271); } function transformLat(x, y) { let ret = -100 + 2 * x + 3 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)); ret += (20 * Math.sin(6 * x * PI) + 20 * Math.sin(2 * x * PI)) * 2 / 3; ret += (20 * Math.sin(y * PI) + 40 * Math.sin(y / 3 * PI)) * 2 / 3; ret += (160 * Math.sin(y / 12 * PI) + 320 * Math.sin(y * PI / 30)) * 2 / 3; return ret; } function transformLon(x, y) { let ret = 300 + x + 2 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)); ret += (20 * Math.sin(6 * x * PI) + 20 * Math.sin(2 * x * PI)) * 2 / 3; ret += (20 * Math.sin(x * PI) + 40 * Math.sin(x / 3 * PI)) * 2 / 3; ret += (150 * Math.sin(x / 12 * PI) + 300 * Math.sin(x * PI / 30)) * 2 / 3; return ret; } function wgs84_to_gcj02(lat, lon) { if (out_of_china(lat, lon)) { return { lat: lat, lon: lon }; } const dlat = transformLat(lon - 105.0, lat - 35.0); const dlng = transformLon(lon - 105.0, lat - 35.0); const radlat = lat / 180.0 * PI; const magic = Math.sin(radlat); const sqrtmagic = Math.sqrt(1 - EE * magic * magic); const dlatFinal = (dlat * 180.0) / ((A * (1 - EE)) / (sqrtmagic * Math.sqrt(sqrtmagic)) * PI); const dlngFinal = (dlng * 180.0) / (A / sqrtmagic * Math.cos(radlat) * PI); return { lat: lat + dlatFinal, lon: lon + dlngFinal }; } // 坐标转换并展示 function convertCoordinates() { const latitudeInput = parseFloat(document.getElementById("latitudeInput").value); const longitudeInput = parseFloat(document.getElementById("longitudeInput").value); if (isNaN(latitudeInput) || isNaN(longitudeInput)) { alert("请输入有效的经纬度!"); return; } // 将经纬度转换为十进制 const latitude = convertToDecimal(latitudeInput); const longitude = convertToDecimal(longitudeInput); // WGS-84坐标显示 document.getElementById('wgsLat').textContent = latitude; document.getElementById('wgsLon').textContent = longitude; // 转换为GCJ-02坐标 const { lat: gcjLat, lon: gcjLon } = wgs84_to_gcj02(latitude, longitude); // GCJ-02坐标显示 document.getElementById('gcjLat').textContent = gcjLat; document.getElementById('gcjLon').textContent = gcjLon; // 更新高德地图链接 document.getElementById('amapLink').href = `https://ditu.amap.com/regeo?lng=${gcjLon}&lat=${gcjLat}&src=uriapi&innersrc=uriapi`; } </script> </head> <body> <div class="container"> <h1>GPS坐标转换与查看</h1> <div> <label for="latitudeInput">请输入纬度:</label> <input type="text" id="latitudeInput" placeholder="例如:2232.490386"> </div> <div> <label for="longitudeInput">请输入经度:</label> <input type="text" id="longitudeInput" placeholder="例如:11404.688746"> </div> <button onclick="convertCoordinates()">转换坐标</button> <div class="results"> <h3>WGS-84坐标:</h3> <p>纬度: <span id="wgsLat">等待数据...</span></p> <p>经度: <span id="wgsLon">等待数据...</span></p> <h3>转换后的GCJ-02坐标:</h3> <p>纬度: <span id="gcjLat">等待数据...</span></p> <p>经度: <span id="gcjLon">等待数据...</span></p> <a class="link-btn" id="amapLink" target="_blank">查看高德地图</a> </div> </div> </body> </html>

http://www.jsqmd.com/news/619815/

相关文章:

  • 数字主权与跨境数据流动:全球开发者的新挑战
  • ROS新手必看:用SolidWorks2020和sw_urdf_exporter快速搭建挖掘机仿真模型
  • 别再只写Hello World了!给你的STM32网关加上MQTT通信和HTTPS安全传输(基于mbedTLS)
  • SCM-03-配置项变更控制报告
  • 从逻辑专家到全能选手:AI模型如何根据任务需求精准选型
  • 含有分布式电源的配电网日前优化调度粒子群算法的MATLAB程序:目标函数为网络损耗与电压偏差的...
  • SQL如何对分组字段进行自定义排序_配合FIELD函数实现
  • Fluent新手避坑:圆柱绕流仿真不收敛?可能是边界层网格没设对(附20层 vs 5层对比案例)
  • 用C#实现三菱PLC控制的那些事儿
  • Gurobi 10.0学术版安装指南:从校园网认证到JupyterLab实战配置
  • 基于 FastAPI + Vue 深度定制的全栈自动化执行引擎设计全解蚁
  • Axure RP 中文界面优化:从语言障碍到流畅设计的智能解决方案
  • IDEA集成开发技巧:利用Phi-3-mini优化Java项目结构与重构代码
  • 2026年江苏教师考编培训哪家好?首选南京苏程教育培训学校 - 小艾信息发布
  • Qwen3-14B效果实测:自动生成Ubuntu系统常见问题解决方案
  • 学习困难的情绪困扰解决方案是什么?
  • 2024年Node.js最佳实践终极指南:102个技巧提升应用性能与稳定性
  • EmulatorJS项目结构深度剖析:理解4.0版本完整重写的架构设计
  • MODIS积雪数据在农业水资源管理中的实战应用:以2000-2020年中国数据集为例
  • AI Coding 工程化革命,Superpowers 管流程,ui-ux-pro-max 管质感
  • ET框架多线程架构演进:从并发困境到纤程模型的性能突破
  • 【JavaScript高级编程】拆解函数流水线 上戏
  • [Linux][虚拟串口]x一个特殊的字节谙
  • JAVA-SSM学习1 Spring-IOCDIBean-上
  • CSL编辑器完全指南:5分钟打造你的专属文献引用样式 ✨
  • Knowledge-Graph项目揭秘:知识图谱与深度学习的完美结合
  • 高效实战:5个AKShare核心技巧实现金融数据分析自动化(2024专业版)
  • 2024年Node.js最佳实践终极指南:102个技巧提升你的后端开发水平
  • ESP32实战-LVGL音乐播放界面移植与优化指南
  • 告别FileZilla!用MobaXterm+Samba在泰山派RK3566上搭建Windows文件共享(保姆级教程)