前些时间在更新我的坐标边界查询工具的时候,需要用到经纬度坐标点的距离计算,和以坐标点为中心生成一个指定距离为半径的圆,搜了一下没有找到现成简单又合适的代码,于是把自己压箱底的代码翻出来了,简化完善了一下,嘿,代码量也不大,还挺好用。

本方法是通过计算得到圆上的多个坐标点,来得到的一个近似的圆形面,只要坐标点够多,这个圆就能足够圆;有了这些坐标点就很容易表示成不同的格式,比如:GeoJSON文本WKT文本Geometry实例

源自 坐标边界查询工具 开源库:https://github.com/xiangyuecn/AreaCity-Query-Geometry (github可以换成gitee),高性能的坐标数据、边界数据查询工具,Java开源程序、带http查询接口,内存占用低(1秒可查1万个以上坐标对应的城市信息)。

java版源码

public static void main(String[] args) {
//计算天坛到天安门的距离
System.out.println(Distance(116.410622, 39.881773, 116.397476, 39.908647));
//生成天坛1公里范围的圆形面
System.out.println(CreateSimpleCircleWKT(116.410622, 39.881773, 1000, 24));
} /** 计算两个坐标的距离,单位米 **/
static public double Distance(double lng1, double lat1, double lng2, double lat2) {
//采用Haversine formula算法,高德地图的js计算代码,比较简洁 https://www.cnblogs.com/ggz19/p/7551088.html
double d=Math.PI/180;
double f=lat1*d, h=lat2*d;
double i=lng2*d - lng1*d;
double e=(1 - Math.cos(h - f) + (1 - Math.cos(i)) * Math.cos(f) * Math.cos(h)) / 2;
return 2 * 6378137 * Math.asin(Math.sqrt(e));
} /** 以坐标点为中心,简单粗略的创建一个指定半径的圆,半径单位米,pointCount为构建圆的坐标点数(比如24个点,点越多越圆,最少3个点),返回构成圆的坐标点数组 **/
static public double[][] CreateSimpleCircle(double lng, double lat, double radius, int pointCount) {
//球面坐标不会算,转换成三角坐标简单点,经度代表值大约:0.01≈1km 0.1≈10km 1≈100km 10≈1000km
double km=radius/1000;
double a=km<5?0.01 :km<50?0.1 :km<500?1 :10;
double b=Distance(lng, lat, lng+a, lat);
double c=Distance(lng, lat, lng, lat+a);
double rb=radius/b*a;
double rc=radius/c*a;
double[][] arr=new double[pointCount+1][];
double n=0,step=360.0/pointCount,N=360-step/2; //注意浮点数±0.000000001的差异
for(int i=0;n<N;i++,n+=step){
double x=lng+rb*Math.cos(n*Math.PI/180);
double y=lat+rc*Math.sin(n*Math.PI/180);
arr[i]=new double[] { x, y };
}
arr[pointCount]=new double[] { arr[0][0], arr[0][1] }; //闭环
return arr;
} /**
以坐标点为中心,简单粗略的创建一个指定半径的圆,半径单位米,pointCount为构建圆的坐标点数(比如24个点,点越多越圆,最少3个点),返回圆的WKT(Well Known Text)文本
,WKT图形绘制预览工具:https://xiangyuecn.gitee.io/areacity-jsspider-statsgov/assets/geo-echarts.html
**/
static public String CreateSimpleCircleWKT(double lng, double lat, double radius, int pointCount) {
double[][] points=CreateSimpleCircle(lng, lat, radius, pointCount);
DecimalFormat df=new DecimalFormat("0.######");
StringBuilder wkt=new StringBuilder("POLYGON((");
for(int i=0;i<points.length;i++) {
if(i>0)wkt.append(",");
wkt.append(df.format(points[i][0])+" "+df.format(points[i][1]));
}
wkt.append("))");
return wkt.toString();
}

js版源码

//测试:计算天坛到天安门的距离
console.log(Distance(116.410622, 39.881773, 116.397476, 39.908647));
//测试:生成天坛1公里范围的圆形面
console.log(CreateSimpleCircleWKT(116.410622, 39.881773, 1000, 24)); /** 计算两个坐标的距离,单位米 **/
function Distance(lng1, lat1, lng2, lat2) {
//采用Haversine formula算法,高德地图的js计算代码,比较简洁 https://www.cnblogs.com/ggz19/p/7551088.html
var d=Math.PI/180;
var f=lat1*d, h=lat2*d;
var i=lng2*d - lng1*d;
var e=(1 - Math.cos(h - f) + (1 - Math.cos(i)) * Math.cos(f) * Math.cos(h)) / 2;
return 2 * 6378137 * Math.asin(Math.sqrt(e));
} /** 以坐标点为中心,简单粗略的创建一个指定半径的圆,半径单位米,pointCount为构建圆的坐标点数(比如24个点,点越多越圆,最少3个点),返回构成圆的坐标点数组 **/
function CreateSimpleCircle(lng, lat, radius, pointCount){
//球面坐标不会算,转换成三角坐标简单点,经度代表值大约:0.01≈1km 0.1≈10km 1≈100km 10≈1000km
var km=radius/1000;
var a=km<5?0.01 :km<50?0.1 :km<500?1 :10;
var b=Distance(lng, lat, lng+a, lat);
var c=Distance(lng, lat, lng, lat+a);
var rb=radius/b*a;
var rc=radius/c*a;
var arr=[];
var n=0,step=360.0/pointCount,N=360-step/2; //注意浮点数±0.000000001的差异
for(var i=0;n<N;i++,n+=step){
var x=lng+rb*Math.cos(n*Math.PI/180);
var y=lat+rc*Math.sin(n*Math.PI/180);
arr[i]=[x, y];
}
arr.push([arr[0][0], arr[0][1]]); //闭环
return arr;
}; /**
以坐标点为中心,简单粗略的创建一个指定半径的圆,半径单位米,pointCount为构建圆的坐标点数(比如24个点,点越多越圆,最少3个点),返回圆的WKT(Well Known Text)文本
,WKT图形绘制预览工具:https://xiangyuecn.gitee.io/areacity-jsspider-statsgov/assets/geo-echarts.html
**/
function CreateSimpleCircleWKT(lng, lat, radius, pointCount){
var points=CreateSimpleCircle(lng, lat, radius, pointCount);
var wkt=["POLYGON(("];
for(var i=0;i<points.length;i++) {
wkt.push((i>0?",":"")+(+points[i][0].toFixed(6))+" "+(+points[i][1].toFixed(6)));
}
wkt.push("))");
return wkt.join("");
};

在线绘制预览效果

生成了圆形面的WKT文本后,可以粘贴进在线预览页面绘制显示:https://xiangyuecn.gitee.io/areacity-jsspider-statsgov/assets/geo-echarts.html ,方便代码调试,地图上有测距功能,可以测量圆面的准确度;页面上的画圆功能,采用的就是js版的代码。

关于计算的精确度

两个经纬度坐标的距离计算,采用的Haversine formula算法,下面的计算代码中高德地图的api同样是Haversine formula算法,和百度地图的计算结果误差在0.2%以内:

bMap=window.BMapGL&&BMapGL.Map.prototype||{getDistance:function(a,b){return BMap.Map.prototype.getDistance(new BMap.Point(a.lng,a.lat),new BMap.Point(b.lng,b.lat)) }};

//地图api计算【纬度】之间的距离,每一度之间的距离是相同的
bMap.getDistance({lng:111,lat:15},{lng:111,lat:16}) //百度111194.86米
new AMap.LngLat(111,15).distance(new AMap.LngLat(111,16)) //高德111319.49米 bMap.getDistance({lng:121,lat:55},{lng:121,lat:56}) //百度111194.78米
new AMap.LngLat(121,55).distance(new AMap.LngLat(121,56)) //高德111319.49米 //地图api计算【经度】之间的距离,会随着纬度的不同而不同
bMap.getDistance({lng:111,lat:15},{lng:112,lat:15}) //百度107405.91米
new AMap.LngLat(111,15).distance(new AMap.LngLat(112,15)) //高德107526.28米 bMap.getDistance({lng:111,lat:55},{lng:112,lat:55}) //百度63778.21米
new AMap.LngLat(111,55).distance(new AMap.LngLat(112,55)) //高德63849.69米
//经度在相同纬度下每一度之间的距离是相同的
bMap.getDistance({lng:121,lat:55},{lng:122,lat:55}) //百度63778.21米
new AMap.LngLat(121,55).distance(new AMap.LngLat(122,55)) //高德63849.69米

对于构成圆的坐标点的计算,使用的以前压箱底的代码,计算比较简单,经度和纬度分别计算出一度的距离长度,在等比例的换算出半径对应的度数大小,比如算出来的经度1°是100km,那么20km半径对应的度数就是1° * 20 / 100 = 0.2°

  • 纬度每一度之间的距离都是固定的长度,等比例换算后的结果是没有误差的。
  • 经度每一度之间的距离长度会随着纬度变化,低纬度长高纬度短,圆面的上下纬度不同,半径小的时候误差小,半径越大误差越大。

所以按等比例换算在纬度上没有问题,但经度上会产生一定的误差,但只要圆的半径不超过10km,误差就能控制在0.5%以内,可通过下面代码直接计算观察到:

//经度之间的距离,不同纬度下的误差计算
d1=new AMap.LngLat(111,15.0).distance(new AMap.LngLat(111.2,15.0));
d2=new AMap.LngLat(111,15.2).distance(new AMap.LngLat(111.2,15.2));
console.log(d2, (d1-d2)/d2*100+"%") //低纬度下经度0.2°距离≈20km,纬度0.2°导致0.09%误差
d1=new AMap.LngLat(111,55.0).distance(new AMap.LngLat(111.2,55.0));
d2=new AMap.LngLat(111,55.2).distance(new AMap.LngLat(111.2,55.2));
console.log(d2, (d1-d2)/d2*100+"%") //高纬度下经度0.2°距离≈10km,纬度0.2°导致0.50%误差 d1=new AMap.LngLat(111,15).distance(new AMap.LngLat(112,15));
d2=new AMap.LngLat(111,16).distance(new AMap.LngLat(112,16));
console.log(d2, (d1-d2)/d2*100+"%") //低纬度下经度1°距离≈100km,纬度1°导致0.49%误差
d1=new AMap.LngLat(111,55).distance(new AMap.LngLat(112,55));
d2=new AMap.LngLat(111,56).distance(new AMap.LngLat(112,56));
console.log(d2, (d1-d2)/d2*100+"%") //高纬度下经度1°距离≈60km,纬度1°导致2.57%误差

由于是通过圆上的坐标点连起来得到的一个圆,本质上是一个近似圆的多边形,只要坐标点足够多就越接近圆,当坐标点少的情况下肉眼可见的不是那么圆(最少3个坐标点,三角形),误差也会很大。但考虑到点数越多,会导致使用上很多地方的计算量会变的很大,所以构成圆的坐标点数也是一个综合考虑的数量,我选择24个坐标点构成一个圆,每个象限6个点,点之间角度为15°。

【完】

经纬度坐标为中心点生成米距离长度半径的圆形面,含java js源码+在线绘制,代码简单零依赖的更多相关文章

  1. 微信小程序一键生成源码 在线制作定制功能强大的微信小程序

    微信小程序发展到现在,短短的一年不到的时间(很快就要迎来微信小程序周年庆),在快迎来周年庆之际,百牛信息技术bainiu.ltd特记录一下这个发展的历程,用于将来见证小程序发展的辉煌时刻,我们还能知道 ...

  2. Kitex源码阅读——脚手架代码是如何通过命令行生成的(一)

    前言 Kitex是字节跳动内部的Golang微服务RPC框架,先已开源. Kitex文档:https://www.cloudwego.io/zh/docs/kitex/getting-started/ ...

  3. 手把手教你基于SqlSugar4编写一个可视化代码生成器(生成实体,以SqlServer为例,文末附源码)

    在开发过程中免不了创建实体类,字段少的表可以手动编写,但是字段多还用手动创建的话不免有些浪费时间,假如一张表有100多个字段,手写有些不现实. 这时我们会借助一些工具,如:动软代码生成器.各种ORM框 ...

  4. Android 二维码 生成和识别(附Demo源码)

    今天讲一下目前移动领域很常用的技术——二维码.现在大街小巷.各大网站都有二维码的踪迹,不管是IOS. Android.WP都有相关支持的软件.之前我就想了解二维码是如何工作,最近因为工作需要使用相关技 ...

  5. 用VC编译lua源码,生成lua语言的解释器和编译器

    用VC编译lua源码,生成lua语言的解释器和编译器 1.去网址下载源码 http://www.lua.org/download.html 2.装一个VC++,我用的是VC6.0 3.接下来我们开始编 ...

  6. 【转】Android 二维码 生成和识别(附Demo源码)--不错

    原文网址:http://www.cnblogs.com/mythou/p/3280023.html 今天讲一下目前移动领域很常用的技术——二维码.现在大街小巷.各大网站都有二维码的踪迹,不管是IOS. ...

  7. Mybatis Generator的model生成中文注释,支持oracle和mysql(通过修改源码的方式来实现)

    在看本篇之前,最好先看一下上一篇通过实现CommentGenerator接口的方法来实现中文注释的例子,因为很多操作和上一篇基本是一致的,所以本篇可能不那么详细. 首先说一下上篇通过实现Comment ...

  8. python生成可执行文件保护源码

    工作中由于需要防止源代泄漏,需要将源代码隐藏,找到两种方法: 1.使用python生成的pyc文件. 这种方法的优点就是pyc文件生成很容易,缺点则是很容易通过工具得到源码,并且python版本不一致 ...

  9. springboot源码解析-管中窥豹系列之bean如何生成?(十四)

    一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...

  10. Java 根据两个经纬度坐标计算距离

    public class Distance{ private static final double EARTH_RADIUS = 6378137;    private static double  ...

随机推荐

  1. JVM 频繁 FULL GC 快速排查整理

    在分享此案例前,先聊聊哪些场景会导致频繁Full GC: 内存泄漏(代码有问题,对象引用没及时释放,导致对象不能及时回收)死循环大对象程序执行了System.gc() 尤其是大对象,80%以上的情况就 ...

  2. 爬取网页的通用代码框架.py(亲测有效)

    import requests def getHTMLText(url): try: kv = {'user-agent':'Mozilla/5.0'} r = requests.get(url,he ...

  3. 第一部分:介绍 Spdlog 日志库

    什么是 Spdlog 日志库 Spdlog 是一个 C++ 的日志库,它具有高效.易用.跨平台等特点.它可以写入到控制台.文件等输出目标,支持多种日志级别.多线程安全等功能,非常适合在 C++ 项目中 ...

  4. php in_array 遍历,in_array大数组查询性能问题

    问题最近在实现一个项目接口的时候发现当数组过大的时候,数据返回的速度有点慢.接口数据返回最长反应时间2s,经过反复调试发现代码段耗时最长的部分在in_array()函数.解决过程在stackoverf ...

  5. 使用Electron-builder将web项目封装客户端安装包 发布

    背景:之前用electron-packager将web项目打包成客户端时,exe文件只能在当前文件夹下运行,如果发送给别人使用 极不方便.所以我们可以用electron-builder将web项目封装 ...

  6. VUEX面试题

    1.你有写过vuex中store的插件吗? 答:没有 2.你有使用过vuex的module吗?主要是在什么场景下使用? 答:把状态全部集中在状态树上,非常难以维护.按模块分成多个module,状态树延 ...

  7. [MAUI 项目实战] 手势控制音乐播放器(三): 动画

    @ 目录 吸附动画 确定位置 平移动画 回弹动画 使用自定义缓动函数 多重动画 点击动画 项目地址 上一章节我们创建了手势容器控件PanContainer,它对拖拽物进行包装并响应了平移手势和点击手势 ...

  8. node服务容器 serve 和 http-serve

    参考:https://www.cnblogs.com/leee/p/5502727.html 项目打包文件本地起serve运行 不能直接双击index.html文件直接运行 放到http容器里运行 s ...

  9. 完美的背景图全屏css代码 – background-size:cover?

    写主题样式的时候经常会碰到用背景图铺满整个背景的需求,这里分享下使用方法 需要的效果 图片以背景的形式铺满整个屏幕,不留空白区域 保持图像的纵横比(图片不变形) 图片居中 不出现滚动条 多浏览器支持 ...

  10. 【Python基础】推导式(列表、字典、集合)

    推导式是一种简洁.高效的语法,用于从一个可迭代对象中生成新的可迭代(iterable)对象. 通常情况下,在以下情况可以考虑使用推导式: 只需要简单的表达式来计算新的可迭代对象的元素. 可迭代对象不是 ...