行驶轨迹
经纬度坐标种类:
我们常用的地图api坐标系有wgs84坐标系,gcj02坐标系,bd09坐标系。
wgs坐标系是国际上通用的坐标系,也称地球坐标系,gps和北斗系统都使用的是wgs坐标系。谷歌地图使用的是wgs坐标系(中国部分除外),openstreetmap使用的也是这种坐标系
gcj02坐标系是由中国国家测绘局制订的地理信息系统的坐标系统。由WGS84坐标系经加密后的坐标系,也称火星坐标系,谷歌中国地图、搜搜中国地图、高德地图采用的是GCJ02地理坐标系。
BD09坐标系:即百度坐标系,GCJ02坐标系经加密后的坐标系,由百度公司独创,百度地图使用的就是这个坐标系。
https://blog.csdn.net/qq_39426934/article/details/90241941
坐标系的转换:
- 通过地图官方的API进行转换,优点是转换准确,缺点是限制次数,批量转换慢,适用于开发阶段
- 通过代码根据卫星椭球坐标投影到平面地图坐标系的投影因子和椭球的偏心率,计算并转换,前端转换节约资源,代码如下:
export default { PI: 3.14159265358979324, x_pi: 3.14159265358979324 * 3000.0 / 180.0, delta: function (lat, lon) { var a = 6378245.0; // a: 卫星椭球坐标投影到平面地图坐标系的投影因子。 var ee = 0.00669342162296594323; // ee: 椭球的偏心率。 var dLat = this.transformLat(lon - 105.0, lat - 35.0); var dLon = this.transformLon(lon - 105.0, lat - 35.0); var radLat = lat / 180.0 * this.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) * this.PI); dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * this.PI); return { 'lat': dLat, 'lon': dLon }; }, // WGS-84 to BD-09 wgs_to_bd: function (wgsLat, wgsLon) { var gc = this.gcj_encrypt(wgsLat, wgsLon); return this.bd_encrypt(gc.lat, gc.lon); }, // WGS-84 to GCJ-02 gcj_encrypt: function (wgsLat, wgsLon) { if (this.outOfChina(wgsLat, wgsLon)) return { 'lat': wgsLat, 'lon': wgsLon }; var d = this.delta(wgsLat, wgsLon); return { 'lat': wgsLat + d.lat, 'lon': wgsLon + d.lon }; }, // GCJ-02 to WGS-84 gcj_decrypt: function (gcjLat, gcjLon) { if (this.outOfChina(gcjLat, gcjLon)) return { 'lat': gcjLat, 'lon': gcjLon }; var d = this.delta(gcjLat, gcjLon); return { 'lat': gcjLat - d.lat, 'lon': gcjLon - d.lon }; }, // GCJ-02 to WGS-84 exactly gcj_decrypt_exact: function (gcjLat, gcjLon) { var initDelta = 0.01; var threshold = 0.000000001; var dLat = initDelta, dLon = initDelta; var mLat = gcjLat - dLat, mLon = gcjLon - dLon; var pLat = gcjLat + dLat, pLon = gcjLon + dLon; var wgsLat, wgsLon, i = 0; while (1) { wgsLat = (mLat + pLat) / 2; wgsLon = (mLon + pLon) / 2; var tmp = this.gcj_encrypt(wgsLat, wgsLon) dLat = tmp.lat - gcjLat; dLon = tmp.lon - gcjLon; if ((Math.abs(dLat) < threshold) && (Math.abs(dLon) < threshold)) break; if (dLat > 0) pLat = wgsLat; else mLat = wgsLat; if (dLon > 0) pLon = wgsLon; else mLon = wgsLon; if (++i > 10000) break; } //console.log(i); return { 'lat': wgsLat, 'lon': wgsLon }; }, // GCJ-02 to BD-09 bd_encrypt: function (gcjLat, gcjLon) { var x = gcjLon, y = gcjLat; var z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * this.x_pi); var theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * this.x_pi); var bdLon = z * Math.cos(theta) + 0.0065; var bdLat = z * Math.sin(theta) + 0.006; return { 'lat': bdLat, 'lon': bdLon }; }, // BD-09 to GCJ-02 bd_decrypt: function (bdLat, bdLon) { var x = bdLon - 0.0065, y = bdLat - 0.006; var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * this.x_pi); var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * this.x_pi); var gcjLon = z * Math.cos(theta); var gcjLat = z * Math.sin(theta); return { 'lat': gcjLat, 'lon': gcjLon }; }, // WGS-84 to Web mercator // mercatorLat -> y mercatorLon -> x mercator_encrypt: function (wgsLat, wgsLon) { var x = wgsLon * 20037508.34 / 180.; var y = Math.log(Math.tan((90. + wgsLat) * this.PI / 360.)) / (this.PI / 180.); y = y * 20037508.34 / 180.; return { 'lat': y, 'lon': x }; }, // Web mercator to WGS-84 // mercatorLat -> y mercatorLon -> x mercator_decrypt: function (mercatorLat, mercatorLon) { var x = mercatorLon / 20037508.34 * 180.; var y = mercatorLat / 20037508.34 * 180.; y = 180 / this.PI * (2 * Math.atan(Math.exp(y * this.PI / 180.)) - this.PI / 2); return { 'lat': y, 'lon': x }; }, // two point's distance distance: function (latA, lonA, latB, lonB) { var earthR = 6371000.; var x = Math.cos(latA * this.PI / 180.) * Math.cos(latB * this.PI / 180.) * Math.cos((lonA - lonB) * this.PI / 180); var y = Math.sin(latA * this.PI / 180.) * Math.sin(latB * this.PI / 180.); var s = x + y; if (s > 1) s = 1; if (s < -1) s = -1; var alpha = Math.acos(s); var distance = alpha * earthR; return distance; }, outOfChina: function (lat, lon) { if (lon < 72.004 || lon > 137.8347) return true; if (lat < 0.8293 || lat > 55.8271) return true; return false; }, transformLat: function (x, y) { var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)); ret += (20.0 * Math.sin(6.0 * x * this.PI) + 20.0 * Math.sin(2.0 * x * this.PI)) * 2.0 / 3.0; ret += (20.0 * Math.sin(y * this.PI) + 40.0 * Math.sin(y / 3.0 * this.PI)) * 2.0 / 3.0; ret += (160.0 * Math.sin(y / 12.0 * this.PI) + 320 * Math.sin(y * this.PI / 30.0)) * 2.0 / 3.0; return ret; }, transformLon: function (x, y) { var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)); ret += (20.0 * Math.sin(6.0 * x * this.PI) + 20.0 * Math.sin(2.0 * x * this.PI)) * 2.0 / 3.0; ret += (20.0 * Math.sin(x * this.PI) + 40.0 * Math.sin(x / 3.0 * this.PI)) * 2.0 / 3.0; ret += (150.0 * Math.sin(x / 12.0 * this.PI) + 300.0 * Math.sin(x / 30.0 * this.PI)) * 2.0 / 3.0; return ret; } };
电子围栏
使用基于postgresql的数据库插件:PostGIS
PostGIS是对象关系型数据库系统PostgreSQL的一个扩展,PostGIS提供如下空间信息服务功能:空间对象、空间索引、空间操作函数和空间操作符。因为PostGIS是建立在PostgreSQL之上的,所以PostGIS自动继承了重要的”企业级”特性以及开放源代码的标准。可以说PostGIS仅仅只是PostgreSQL的一个插件,但是它将PostgreSQL变成了一个强大的空间数据库!
围栏计算可以基于数据库空间函数 ST_Witin
ST_Within(geometry A, geometry B):
如果几何A完全在几何B内,则返回true。为了使这个函数有意义,源几何图形必须具有相同的坐标投影,具有相同的SRID。如果(a,B)中的ST为真,且(B, a)中的ST为真,则认为这两个几何图形在空间上是相等的。
参考:
https://postgis.net/docs/manual-2.5/reference.html
https://github.com/digoal/blog/blob/master/201708/20170803_01.md
通过PHP代码计算电子围栏
推荐包:phpgeo
数据统计
本车联网项目的数据量为亿万级,由于数据量庞大,传统关系型数据库如mysql已经无法满足业务需求,所以我们就会用到时序数据库
随着物联网的发展,时序数据库的需求越来越多,比如水文监控、工厂的设备监控、国家安全相关的数据监控、通讯监控、金融行业指标数据、传感器数据等。
时序数据有几个特点:
1. 基本上都是插入,没有更新的需求。
2. 数据基本上都有时间属性,随着时间的推移不断产生新的数据,旧的数据不需要保存太久。
业务方对时序数据通常有几个查询需求:
1. 获取最新状态,查询最近的数据(例如传感器最新的状态)
2. 展示区间统计,指定时间范围,查询统计信息,例如平均值,最大值,最小值,计数等。。。
3. 获取异常数据,根据指定条件,筛选异常数据
某项目使用到了TimescaleDB时序数据库
TimescaleDB其实是基于PostgreSQL的时序数据库插件,完全继承了PostgreSQL的功能,对于复杂查询,各种类型(GIS,json,k-v,图像特征值,range,数组,复合类型,自定义类型,…..)的支持非常丰富,非常适合工业化的时序数据库场景需求。
这个数据库具有以下特点:
1. 基于时序优化
2. 自动分片(按时间、空间自动分片(chunk))
3. 全SQL接口
4. 支持垂直于横向扩展
5. 支持时间维度、空间维度自动分区。空间维度指属性字段(例如传感器ID,用户ID等)
6. 支持多个SERVER,多个CHUNK的并行查询。分区在TimescaleDB中被称为chunk。
7. 自动调整CHUNK的大小
8. 内部写优化(批量提交、内存索引、事务支持、数据倒灌)。
参考:
https://github.com/digoal/blog/blob/master/201704/20170409_05.md
https://github.com/mjaschen/phpgeo
https://phpgeo.marcusjaschen.de/Calculations/Geofence.html