使用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业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
微服务注册中心注册表与hashcode实现golang版
背景 基于负载均衡的服务调用 基于负载均衡的服务相互调用指的是通过基于Lvs、Haproxy、Nginx等负载均衡软件来构建一个负载均衡服务,所有的服务调用都通过负载均衡器 从负载均衡的这种模式下其实有两个主要的问题: 一是中心化,整个系统都基于负载均衡器,负载均衡就相当于整个业务的中心,虽然我们可以通过一些高可用手段来保证,但其实内部流量通常是巨大的,很容易出现性能瓶颈 二是增加了一次TCP交互 当然也有很多好处,比如可以做一些负载均衡、长链接维护、分布式跟踪等,这不是本文重点 基于注册中心的服务调用 所有的服务都启动后都通过注册中心来注册自己,同时把注册中心里面的服务信息拉回本地,后续调用,就直接检查本地的服务和节点信息来进行服务节点的调用 注册中心中的注册表 每个服务节点都会来注册中心进行服务注册,那数据如何在服务端进行保存呢,其实就是注册表,其实等同于windows 里面的注册表,每个服务都来注册,把自己的信息上报上来,然后注册中心吧注册表,返回给client端,那服务之间就知道要调用服务的节点啦 注册中心事件队列 微服务注册注册中心通常会大量的服务注册, 那不能每次客户端来请...
- 下一篇
TalkingData的Spark On Kubernetes实践
众所周知,Spark是一个快速、通用的大规模数据处理平台,和Hadoop的MapReduce计算框架类似。但是相对于MapReduce,Spark凭借其可伸缩、基于内存计算等特点,以及可以直接读写Hadoop上任何格式数据的优势,使批处理更加高效,并有更低的延迟。实际上,Spark已经成为轻量级大数据快速处理的统一平台。 Spark作为一个数据计算平台和框架,更多的是关注Spark Application的管理,而底层实际的资源调度和管理更多的是依靠外部平台的支持: Spark官方支持四种Cluster Manager:Spark standalone cluster manager、Mesos、YARN和Kubernetes。由于我们TalkingData是使用Kubernetes作为资源的调度和管理平台,所以Spark On Kubernetes对于我们是最好的解决方案。 如何搭建生产可用的Kubernetes集群 部署 目前市面上有很多搭建Kubernetes的方法,比如Scratch、Kubeadm、Minikube或者各种托管方案。因为我们需要简单快速地搭建功能验证集群,所以...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS关闭SELinux安全模块
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2更换Tomcat为Jetty,小型站点的福音