前些時間在更新我的座標邊界查詢工具的時候,需要用到經緯度座標點的距離計算,和以座標點為中心生成一個指定距離為半徑的圓,搜了一下沒有找到現成簡單又合適的程式碼,於是把自己壓箱底的程式碼翻出來了,簡化完善了一下,嘿,程式碼量也不大,還挺好用。
本方法是通過計算得到圓上的多個座標點,來得到的一個近似的圓形面,只要座標點夠多,這個圓就能足夠圓;有了這些座標點就很容易表示成不同的格式,比如:GeoJSON文字
、WKT文字
、Geometry範例
。
源自 座標邊界查詢工具 開源庫:https://github.com/xiangyuecn/AreaCity-Query-Geometry (github可以換成gitee),高效能的座標資料、邊界資料查詢工具,Java開源程式、帶http查詢介面,記憶體佔用低(1秒可查1萬個以上座標對應的城市資訊)。
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();
}
//測試:計算天壇到天安門的距離
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°。
【完】