乐趣区

关于前端:经纬度坐标为中心点生成米距离长度半径的圆形面含java-js源码在线绘制代码简单零依赖

前些时间在更新我的坐标边界查问工具的时候,须要用到经纬度坐标点的间隔计算,和以坐标点为核心生成一个指定间隔为半径的圆,搜了一下没有找到现成简略又适合的代码,于是把本人压箱底的代码翻出来了,简化欠缺了一下,嘿,代码量也不大,还挺好用。

本办法是通过计算失去圆上的多个坐标点,来失去的一个近似的圆形面,只有坐标点够多,这个圆就能足够圆;有了这些坐标点就很容易示意成不同的格局,比方: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°。

【完】

退出移动版