使用 Node-Red 对 Volvo On Call 的地理位置纠偏
背景
最近在 Home Assistant 中接入了官方集成 Volvo On Call,确实能很方便的获取车辆各种信息以及控制车辆。但有一个问题就是 Volvo On Call 上传的地理坐标是 GCJ-02 火星坐标系,而 Home Assistant 使用的是 WGS84 坐标系,这会导致 Volvo 车辆的坐标产生偏离,比如判定车辆是否在家中都会产生问题。
解决方案
大致流程
- 在 Home Assistant 生成一个新的
device tracker
- 使用 node-red 轮询 volvo on call 上报的坐标,并经过 js 脚本转换坐标,再调用
device_tracker.see
服务更新第1步中的device tracker
坐标 - 使用第1步中的
device tracker
作为实体在 Home Assistant 中进行地理距离的判定运算等
详细步骤
在
/config/configuration.yaml
中新增 volvooncall 配置。更多参考官方教程。volvooncall: username: !secret volvooncall_username password: !secret volvooncall_password region: cn
重启 Home Assistant 使
volvooncall
集成生效,并观察/config/known_devices.yaml
中是否多了一个volvo_xxxx
自动生成的配置,如下volvo_xxxxxx: name: xxxxxx mac: icon: mdi:car picture: track: true
在
/config/known_devices.yaml
中手动新增一个配置(将用于代替上方的volvo_xxxxxx
)volvo_car: name: Volvo Car mac: icon: track: true
重启 Home Assistant,此时你应该能在
开发者工具
中找到第3步手动生成的新实体device_tracker.volvo_car
source_type: null friendly_name: Volvo Car
- 打开
Node-RED
配置页面,接下来进行相关配置 - 新增一个
poll state
节点,用于获取第2步中的volvo_xxxxxx
地理坐标 新增一个
function
节点,用于计算新的地理坐标,并在函数
中输入如下代码var x_PI = 3.14159265358979324 * 3000.0 / 180.0; var PI = 3.1415926535897932384626; var a = 6378245.0; var ee = 0.00669342162296594323; /** ,* 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02) 的转换 ,* 即 百度 转 谷歌、高德 ,* @param bd_lng ,* @param bd_lat ,* @returns {*[]} ,*/ var bd09togcj02 = function bd09togcj02(bd_lng, bd_lat) { var bd_lng = +bd_lng; var bd_lat = +bd_lat; var x = bd_lng - 0.0065; var y = bd_lat - 0.006; var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_PI); var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_PI); var gg_lng = z * Math.cos(theta); var gg_lat = z * Math.sin(theta); return [gg_lng, gg_lat] }; /** ,* 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换 ,* 即 谷歌、高德 转 百度 ,* @param lng ,* @param lat ,* @returns {*[]} ,*/ var gcj02tobd09 = function gcj02tobd09(lng, lat) { var lat = +lat; var lng = +lng; var z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI); var theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI); var bd_lng = z * Math.cos(theta) + 0.0065; var bd_lat = z * Math.sin(theta) + 0.006; return [bd_lng, bd_lat] }; /** ,* WGS-84 转 GCJ-02 ,* @param lng ,* @param lat ,* @returns {*[]} ,*/ var wgs84togcj02 = function wgs84togcj02(lng, lat) { var lat = +lat; var lng = +lng; if (out_of_china(lng, lat)) { return [lng, lat] } else { var dlat = transformlat(lng - 105.0, lat - 35.0); var dlng = transformlng(lng - 105.0, lat - 35.0); var radlat = lat / 180.0 * PI; var magic = Math.sin(radlat); magic = 1 - ee * magic * magic; var sqrtmagic = Math.sqrt(magic); dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI); dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI); var mglat = lat + dlat; var mglng = lng + dlng; return [mglng, mglat] } }; /** ,* GCJ-02 转换为 WGS-84 ,* @param lng ,* @param lat ,* @returns {*[]} ,*/ var gcj02towgs84 = function gcj02towgs84(lng, lat) { var lat = +lat; var lng = +lng; if (out_of_china(lng, lat)) { return [lng, lat] } else { var dlat = transformlat(lng - 105.0, lat - 35.0); var dlng = transformlng(lng - 105.0, lat - 35.0); var radlat = lat / 180.0 * PI; var magic = Math.sin(radlat); magic = 1 - ee * magic * magic; var sqrtmagic = Math.sqrt(magic); dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI); dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI); var mglat = lat + dlat; var mglng = lng + dlng; return [lng * 2 - mglng, lat * 2 - mglat] } }; var transformlat = function transformlat(lng, lat) { var lat = +lat; var lng = +lng; var ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0; ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0; return ret }; var transformlng = function transformlng(lng, lat) { var lat = +lat; var lng = +lng; var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0; ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0; return ret }; /** ,* 判断是否在国内,不在国内则不做偏移 ,* @param lng ,* @param lat ,* @returns {boolean} ,*/ var out_of_china = function out_of_china(lng, lat) { var lat = +lat; var lng = +lng; // 纬度 3.86~53.55, 经度 73.66~135.05 return !(lng > 73.66 && lng < 135.05 && lat > 3.86 && lat < 53.55); }; var newLocation = gcj02towgs84(msg.data.attributes.longitude, msg.data.attributes.latitude) msg.payload = {} msg.payload.longitude = newLocation[0] msg.payload.latitude = newLocation[1] return msg;
新增一个
call service
节点,用于更新第3步中手动创建的实体地理坐标Domain: device_tracker Service: see Data: { "dev_id":"volvo_car", "gps":[payload.latitude, payload.longitude] }
- 将上述3个节点依次连接,并点击“部署”按钮
- 回到 Home Assistant 的“开发者工具”页面,搜索
device_tracker.volvo_car
,此时就能看到纠偏后的地理坐标
附
node-red
一键导入
[
{
"id": "eaae412a3cb97052",
"type": "poll-state",
"z": "b30b6133eb27f27e",
"name": "",
"server": "fbec60d9.9bf86",
"version": 2,
"exposeToHomeAssistant": false,
"haConfig": [
{
"property": "name",
"value": ""
},
{
"property": "icon",
"value": ""
}
],
"updateinterval": "60",
"updateIntervalType": "num",
"updateIntervalUnits": "seconds",
"outputinitially": false,
"outputonchanged": false,
"entity_id": "device_tracker.volvo_xxxxxx",
"state_type": "str",
"halt_if": "",
"halt_if_type": "str",
"halt_if_compare": "is",
"outputs": 1,
"x": 250,
"y": 200,
"wires": [
[
"38ed7f10fa11c589"
]
]
},
{
"id": "38ed7f10fa11c589",
"type": "function",
"z": "b30b6133eb27f27e",
"name": "",
"func": "var x_PI = 3.14159265358979324 * 3000.0 / 180.0;\nvar PI = 3.1415926535897932384626;\nvar a = 6378245.0;\nvar ee = 0.00669342162296594323;\n/**\n * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02) 的转换\n * 即 百度 转 谷歌、高德\n * @param bd_lng\n * @param bd_lat\n * @returns {*[]}\n */\nvar bd09togcj02 = function bd09togcj02(bd_lng, bd_lat) {\n var bd_lng = +bd_lng;\n var bd_lat = +bd_lat;\n var x = bd_lng - 0.0065;\n var y = bd_lat - 0.006;\n var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_PI);\n var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_PI);\n var gg_lng = z * Math.cos(theta);\n var gg_lat = z * Math.sin(theta);\n return [gg_lng, gg_lat]\n};\n\n/**\n * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换\n * 即 谷歌、高德 转 百度\n * @param lng\n * @param lat\n * @returns {*[]}\n */\nvar gcj02tobd09 = function gcj02tobd09(lng, lat) {\n var lat = +lat;\n var lng = +lng;\n var z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI);\n var theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI);\n var bd_lng = z * Math.cos(theta) + 0.0065;\n var bd_lat = z * Math.sin(theta) + 0.006;\n return [bd_lng, bd_lat]\n};\n\n/**\n * WGS-84 转 GCJ-02\n * @param lng\n * @param lat\n * @returns {*[]}\n */\nvar wgs84togcj02 = function wgs84togcj02(lng, lat) {\n var lat = +lat;\n var lng = +lng;\n if (out_of_china(lng, lat)) {\n return [lng, lat]\n } else {\n var dlat = transformlat(lng - 105.0, lat - 35.0);\n var dlng = transformlng(lng - 105.0, lat - 35.0);\n var radlat = lat / 180.0 * PI;\n var magic = Math.sin(radlat);\n magic = 1 - ee * magic * magic;\n var sqrtmagic = Math.sqrt(magic);\n dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);\n dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);\n var mglat = lat + dlat;\n var mglng = lng + dlng;\n return [mglng, mglat]\n }\n};\n\n/**\n * GCJ-02 转换为 WGS-84\n * @param lng\n * @param lat\n * @returns {*[]}\n */\nvar gcj02towgs84 = function gcj02towgs84(lng, lat) {\n var lat = +lat;\n var lng = +lng;\n if (out_of_china(lng, lat)) {\n return [lng, lat]\n } else {\n var dlat = transformlat(lng - 105.0, lat - 35.0);\n var dlng = transformlng(lng - 105.0, lat - 35.0);\n var radlat = lat / 180.0 * PI;\n var magic = Math.sin(radlat);\n magic = 1 - ee * magic * magic;\n var sqrtmagic = Math.sqrt(magic);\n dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);\n dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);\n var mglat = lat + dlat;\n var mglng = lng + dlng;\n return [lng * 2 - mglng, lat * 2 - mglat]\n }\n};\n\nvar transformlat = function transformlat(lng, lat) {\n var lat = +lat;\n var lng = +lng;\n var ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));\n ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;\n ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0;\n ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0;\n return ret\n};\n\nvar transformlng = function transformlng(lng, lat) {\n var lat = +lat;\n var lng = +lng;\n var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));\n ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;\n ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0;\n ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0;\n return ret\n};\n\n/**\n * 判断是否在国内,不在国内则不做偏移\n * @param lng\n * @param lat\n * @returns {boolean}\n */\nvar out_of_china = function out_of_china(lng, lat) {\n var lat = +lat;\n var lng = +lng;\n // 纬度 3.86~53.55, 经度 73.66~135.05 \n return !(lng > 73.66 && lng < 135.05 && lat > 3.86 && lat < 53.55);\n};\n\nvar newLocation = gcj02towgs84(msg.data.attributes.longitude, msg.data.attributes.latitude)\n\nmsg.payload = {}\nmsg.payload.longitude = newLocation[0]\nmsg.payload.latitude = newLocation[1]\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 120,
"y": 320,
"wires": [
[
"8e828fb4ce44b55c"
]
]
},
{
"id": "8e828fb4ce44b55c",
"type": "api-call-service",
"z": "b30b6133eb27f27e",
"name": "",
"server": "fbec60d9.9bf86",
"version": 3,
"debugenabled": false,
"service_domain": "device_tracker",
"service": "see",
"entityId": "",
"data": "{ \"dev_id\":\"volvo_car\", \"gps\":[payload.latitude, payload.longitude] }",
"dataType": "jsonata",
"mergecontext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "none",
"x": 180,
"y": 420,
"wires": [
[]
]
},
{
"id": "fbec60d9.9bf86",
"type": "server",
"name": "Home Assistant",
"version": 2,
"addon": true,
"rejectUnauthorizedCerts": true,
"ha_boolean": "y|yes|true|on|home|open",
"connectionDelay": true,
"cacheJson": true,
"heartbeat": false,
"heartbeatInterval": 30
}
]