使用Path2D和凸包算法实现地理围栏服务
前言
地理围栏(Geo-fencing)是LBS的一种新应用,就是用一个虚拟的栅栏围出一个虚拟地理边界。在物流配送行业应用比较广,划分每个配送网点或者商家配送的范围,提高配送员的配送效率和服务的范围。
1.使用Path2D创建一个多边形
Path2D类是java.awt.geom包提供的工具包,可表示任意几何路径的简单而灵活的形状。它可以完全表示PathIterator接口可以迭代的任何路径, 包括其所有段类型和绕组规则,并且它实现了Shape接口的所有基本命中测试方法。
使用Path2D.Float带有可表示且能使用浮点精度的数据的时候。使用Path2D.Double 对于需要双精度的准确性或范围的数据。
先通过高德地图在线编辑一个多边形覆盖图,然后获取到有序的坐标
https://lbs.amap.com/api/javascript-api/example/overlayers/polygon-draw-and-edit
代码示例如下:
//传参 有序的坐标范围
public static Path2D.Double create(List<PointDouble> polygon) {
//创建path2D对象
Path2D.Double generalPath = new Path2D.Double();
//获取有序坐标范围的第一个坐标
PointDouble first = polygon.get(0);
//通过移动到指定坐标(以双精度指定),将一个点添加到路径中
generalPath.moveTo(first.getX(), first.getY());
//删除有序坐标范围第一个
polygon.remove(0);
//遍历有序坐标范围后面的坐标
for (PointDouble d : polygon) {
// 通过绘制一条从当前坐标到新指定坐标(以双精度指定)的直线,将一个点添加到路径中。
generalPath.lineTo(d.getX(), d.getY());
}
// 将几何多边形封闭
generalPath.lineTo(first.getX(), first.getY());
//关闭路径
generalPath.closePath();
return generalPath;
}
以上用到了的方法详解
moveTo(double x, double y)
通过移动到以double精度指定的指定坐标,向路径添加一个点。
lineTo(double x, double y)
通过从当前坐标绘制直线到以double精度指定的新指定坐标,将路径添加到路径。
closePath()
通过将直线绘制回最后一个坐标来关闭当前子路径moveTo
。
2.判断某个坐标是否在Path2D
代码示例
PointDouble point = new PointDouble(116.403322,39.920255);
//生成好的多边形是不是包含某个坐标
path2d.contains(point)
以上用到了的方法详解
contains(double x, double y)
测试指定坐标是否在边界内Shape
3.判断某个矩形区域是否在Path2D
contains(PathIterator pi, double x, double y, double w, double h)
测试指定的矩形区域是否完全位于指定的闭合边界内PathIterator
4.使用凸包算法把指定Path2D转换成一块大的覆盖面
凸包(Convex Hull)是一个计算几何(图形学)中的概念。在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。X的凸包可以用X内所有点(X1,...Xn)的凸组合来构造.在二维欧几里得空间中,凸包可想象为一条刚好包著所有点的橡皮圈。用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点。
代码示例:
/**
* 凸包算法
*
* @author wangnian
*/
public class ConvexUtil {
private static int MAX_ANGLE = 4;
private double currentMinAngle = 0;
private List<PointDouble> hullPointList;
private List<Integer> indexList;
private List<PointDouble> pointDoubles;
private int firstIndex;
/**
* 获取凸包算法之后的坐标
*
* @param pointDoubleList
* @return
*/
public static List<PointDouble> getConvexPoint(List<PointDouble> pointDoubleList) {
//排重一下
pointDoubleList = pointDoubleList.stream().distinct().collect(Collectors.toList());
ConvexUtil convexUtil = new ConvexUtil(pointDoubleList);
return convexUtil.calculateHull();
}
public ConvexUtil() {
}
public ConvexUtil(List<PointDouble> pointDoubleList) {
this.hullPointList = new LinkedList<>();
this.indexList = new LinkedList<>();
this.pointDoubles = pointDoubleList;
firstIndex = getFirstPoint(pointDoubleList);
addToHull(firstIndex);
}
public List<PointDouble> calculateHull() {
for (int i = getNextIndex(firstIndex); i != firstIndex; i = getNextIndex(i)) {
addToHull(i);
}
return showHullPoints();
}
private void addToHull(int index) {
indexList.add(index);
hullPointList.add(pointDoubles.get(index));
}
private List<PointDouble> showHullPoints() {
Iterator<PointDouble> itPoint = hullPointList.iterator();
List<PointDouble> resultList = new ArrayList<>();
while (itPoint.hasNext()) {
PointDouble p = itPoint.next();
resultList.add(new PointDouble(p.getX(), p.getY()));
}
return resultList;
}
private int getNextIndex(int currentIndex) {
double minAngle = MAX_ANGLE;
double pseudoAngle;
int minIndex = 0;
for (int i = 0; i < pointDoubles.size(); i++) {
if (i != currentIndex) {
pseudoAngle = getPseudoAngle(pointDoubles.get(i).getX() - pointDoubles.get(currentIndex).getX(),
pointDoubles.get(i).getY() - pointDoubles.get(currentIndex).getY());
if (pseudoAngle >= currentMinAngle && pseudoAngle < minAngle) {
minAngle = pseudoAngle;
minIndex = i;
} else if (pseudoAngle == minAngle) {
if ((abs(pointDoubles.get(i).getX() - pointDoubles.get(currentIndex).getX()) >
abs(pointDoubles.get(minIndex).getX() - pointDoubles.get(currentIndex).getX()))
|| (abs(pointDoubles.get(i).getY() - pointDoubles.get(currentIndex).getY()) >
abs(pointDoubles.get(minIndex).getY() - pointDoubles.get(currentIndex).getY()))) {
minIndex = i;
}
}
}
}
currentMinAngle = minAngle;
return minIndex;
}
private double getPseudoAngle(double dx, double dy) {
if (dx > 0 && dy >= 0) {
return dy / (dx + dy);
}
if (dx <= 0 && dy > 0) {
return 1 + (abs(dx) / (abs(dx) + dy));
}
if (dx < 0 && dy <= 0) {
return 2 + (dy / (dx + dy));
}
if (dx >= 0 && dy < 0) {
return 3 + (dx / (dx + abs(dy)));
}
throw new Error("坐标有重复");
}
private int getFirstPoint(List<PointDouble> pointDoubleList) {
int minIndex = 0;
for (int i = 1; i < pointDoubleList.size(); i++) {
if (pointDoubleList.get(i).getY() < pointDoubleList.get(minIndex).getY()) {
minIndex = i;
} else if ((pointDoubleList.get(i).getY() == pointDoubleList.get(minIndex).getY())
&& (pointDoubleList.get(i).getX() < pointDoubleList.get(minIndex).getX())) {
minIndex = i;
}
}
return minIndex;
}
public static void main(String[] args) {
List<PointDouble> resultList = new ArrayList() {{
add(new PointDouble(1.0, 4.0));
add(new PointDouble(1.0, 4.0));
add(new PointDouble(2.0, 2.0));
add(new PointDouble(2.0, 2.0));
add(new PointDouble(3.0, 3.0));
add(new PointDouble(3.0, 3.0));
add(new PointDouble(4.0, 4.0));
add(new PointDouble(4.0, 4.0));
add(new PointDouble(4.0, 2.0));
add(new PointDouble(4.0, 2.0));
add(new PointDouble(5.0, 2.0));
add(new PointDouble(5.0, 2.0));
}};
System.out.println(ConvexUtil.getConvexPoint(resultList));
}
5. 根据当前地图窗口查询所有相交Path2D
根据当前地图显示范围获取到northeast东北角和southwest西南角的坐标位置,查询相交的所有Path2D
高德地图示例地址:
https://lbs.amap.com/api/javascript-api/example/map/map-bounds/?sug_index=2
代码示例
//获取西南的纬度 也就是X
double lng = southwest.getLng();
//获取西南的经度 也就是Y
double lat = southwest.getLat();
//东北角的X减去西南角的X ,得到宽
double w = northeast.getLng() - southwest.getLng();
//东北角的Y减去西南角的Y ,得到高
double h = northeast.getLat() - southwest.getLat();
//判断是否相交
Path2D.intersects(lng, lat, w, h)
以上用到了的方法详解
intersects(double x, double y, double w, double h)
测试内部是否与Shape指定矩形区域的内部相交。
提示: 以上只是一些关键的局部代码,在实际应用中,需要将所有的范围对象按照凸包算法或者其他纬度的行政区域进行分类并缓存,方便快速遍历查询。
欢迎大家关注凯京技术团队 https://my.oschina.net/keking 很多专业的干活技术分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
Go语言中的代码重用 - 继承还是组合?
故事要从我在一个项目中,想要假装的专业一点而遇到的一个陷阱说起。 代码重用 在这个项目中,我们已经有了类似如下的代码: package main import ( "fmt" ) func main() { user := &User{name: "Chris"} user.sayHi() } type User struct { name string } func (u *User) sayHi() { u.sayName() u.sayType() } func (u *User) sayName() { fmt.Printf("I am %s.", u.name) } func (u *User) sayType() { fmt.Println("I am a user.") } I am Chris.I am a user. 然后我接到的新需求是这样的,我需要开发一种新的用户,它和当前这种用户有一些相同的行为。当然,最主要的是也有很多不同的行为。作为一名老司机,我当然知道,这些不同的地方才是我需要重点关注并且实现的。 为了区分这两种用户,我们就叫他们普通用户和文艺用户...
-
下一篇
死磕 java同步系列之自己动手写一个锁Lock
问题 (1)自己动手写一个锁需要哪些知识? (2)自己动手写一个锁到底有多简单? (3)自己能不能写出来一个完美的锁? 简介 本篇文章的目标一是自己动手写一个锁,这个锁的功能很简单,能进行正常的加锁、解锁操作。 本篇文章的目标二是通过自己动手写一个锁,能更好地理解后面章节将要学习的AQS及各种同步器实现的原理。 分析 自己动手写一个锁需要准备些什么呢? 首先,在上一章学习synchronized的时候我们说过它的实现原理是更改对象头中的MarkWord,标记为已加锁或未加锁。 但是,我们自己是无法修改对象头信息的,那么我们可不可以用一个变量来代替呢? 比如,这个变量的值为1的时候就说明已加锁,变量值为0的时候就说明未加锁,我觉得可行。 其次,我们要保证多个线程对上面我们定义的变量的争用是可控的,所谓可控即同时只能有一个线程把它的值修改为1,且当它的值为1的时候其它线程不能再修改它的值,这种是不是就是典型的CAS操作,所以我们需要使用Unsafe这个类来做CAS操作。 然后,我们知道在多线程的环境下,多个线程对同一个锁的争用肯定只有一个能成功,那么,其它的线程就要排队,所以我们还需要一个...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker容器配置,解决镜像无法拉取问题
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- 2048小游戏-低调大师作品
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- MySQL数据库在高并发下的优化方案