由电子围栏引发的经纬度计算
- 什么是电子围栏?
就是跟孙悟空那个一样,画个圈,进入会有信息警告。
-
类型有哪些?
- 圆形的:适用于某个地点,比如说家里,小孩子离开家的半径100米就会发出警告,这个100米是半径参数;
- 线性的:适用于上学,放学路线,比如说离开设定路线100米,就会发出警告,这个100米为距离;
- 多边型:不规则的多边型,适用于区域,比如说学校,把学校画个多边型,离开了这个多边型就会触发警告。
当然,还有进入的警告,这个看需求。
花钱的实现方式
- 高德
- 百度
- 腾讯
都有的
腾讯的参考
https://lbs.qq.com/service/placeCloud/placeCloudGuide/cloudOverview
配额限制:
百度的参考
https://lbs.baidu.com/faq/api?title=yingyan/guide/geo-fence
配额限制:
高德地图参考:
https://lbs.amap.com/api/track/lieying-kaifa/api/track_fence
配额限制:
于是我打算自己做一个!
思路
- 对于圆形的围栏,这个我会,小时候我学过点到圆心的距离
- 如果等于半径,就在圆上,如果大于半径,就在圆外,如果小于半径,就在圆内。
- 对于线性的围栏,也可以用同样的知识来解决,求当前点与线上每一点的距离,然后找出最小距离的作为目标比较,如果是大于设定的距离,那么在线外,如果小于目标距离,在线内。
- 多边形的,可以通过射线方程计算交点,根据交点个数判断在多边形内还是多边形外:
- 任意射线与多边形的交点个数是奇数,则点在多边形内,是偶数,则点在多边形外
数据表结构
- id ID
- fence_name 围栏名称
- type 类型:line shape circle
- points 点列表
- distance 距离(line和circle时必填)
- create_time 创建时间
- update_time 更新时间
主要代码
查询和围栏入库的代码我就不贴了
贴一下关键的代码
求两个经纬度之间的距离
/**
* @param lat1 第一点的纬度
* @param lon1 第一点的经度
* @param lat2 第二点的纬度
* @param lon2 第二点的经度
* @return 两点间的大圆距离(米)
* socketHandler.distance("12345678", 23.118, 113.368, 23.077114, 113.345843);
*/
public double distance(String deviceId, double lat1, double lon1, double lat2, double lon2) {
log.info("distance ==> {} {} {} {} {}", deviceId, lat1, lon1, lat2, lon2);
Point source = new Point(lon1, lat1);
redisTemplate.opsForGeo().add(deviceId, source, "source");
Point target = new Point(lon2, lat2);
redisTemplate.opsForGeo().add(deviceId, target, "target");
Distance distance = redisTemplate.opsForGeo().distance(deviceId, "source", "target", Metrics.KILOMETERS);
if (distance != null) {
double value = distance.getValue();
log.info("value ==> {}", value);
//单位米
return value * 1000;
}
redisTemplate.opsForGeo().remove(deviceId, "source", "target");
return 0;
}
@Autowired
private RedisTemplate<String, String> redisTemplate;
这里我直接用了redis来求两个经纬度坐标点之间的距离,大家也可以自己写。
点与多边形的关系
/**
* 判断一个点是否位于一个多边形内部。
*
* @param pointX 目标点的经度
* @param pointY 目标点的纬度
* @param polygon 多边形的顶点列表
* @return 如果点在多边形内则返回 true,否则返回 false
*/
public static boolean isPointInPolygon(double pointX, double pointY, List<String> polygon) {
int n = polygon.size();
boolean inside = false;
String pointItem1 = polygon.get(0);
String[] split1 = pointItem1.split(",");
double p10 = Double.parseDouble(split1[0]);
double p11 = Double.parseDouble(split1[1]);
for (int i = 0; i < n + 1; i++) {
String pointItem2 = polygon.get(i % n);
String[] split2 = pointItem2.split(",");
double p20 = Double.parseDouble(split2[0]);
double p21 = Double.parseDouble(split2[1]);
if (pointY > Math.min(p11, p21)) {
if (pointY <= Math.max(p11, p21)) {
if (pointX <= Math.max(p10, p20)) {
if (p11 != p21) {
double xints = (pointY - p11) * (p20 - p10) / (p21 - p11) + p10;
if (p10 == p20 || pointX <= xints) {
inside = !inside;
}
}
}
}
}
p10 = p20;
p11 = p21;
}
return inside;
}