这个用于地理定位搜索的 longitude/latitude 边界框算法有什么问题?
What is the issue with this longitude/latitude bounding box algorithm for geolocation searching?
我有一小段代码用于查找某个位置的边界框,以便在半径范围内搜索用户个人资料。它几乎按照我的意愿工作,但不幸的是最终值有点失真。例如,如果我将 50 英里传递到函数中,我得到的点可能距原点 70 英里而不是 50 英里。我认为这与我正在计算的常量之一略有偏差有关。
常量:
const RAD_CON = Math.PI / 180; // Radian conversion constant
const R_EARTH = 6378; // Radius of the earth in Km
const KM_PER_MILE = 1.609344; // Km/mile conversion constant
// I think this might be wrong
const METER_DEGREES = (1 / ((2 * Math.PI / 360) * R_EARTH)) / 1000; // Meters per degree lat/lng
违规代码:
_getBoundingCoords(location: Location, miles: number) {
const meters = miles * KM_PER_MILE * 1000;
const latNorth = location.lat + (meters * METER_DEGREES);
const latSouth = location.lat - (meters * METER_DEGREES);
const lngWest = location.lng + (meters * METER_DEGREES) / Math.cos(location.lat * RAD_CON);
const lngEast = location.lng - (meters * METER_DEGREES) / Math.cos(location.lat * RAD_CON);
return [latNorth, latSouth, lngWest, lngEast];
}
如果您想知道我是如何使用它的,我将使用返回值来为 mongodb 集合构建查询。查询到的数据格式为
{
lat: number;
lng: number;
}
查询:
const query = {
$and: [{
'location.lat': {
$lte: latNorth,
$gte: latSouth,
},
}, {
'location.lng': {
$lte: lngWest,
$gte: lngEast,
},
}],
};
有一个 lot 与计算纬度和经度坐标相关的数学,在...[=14 有非常详细的解释=]
幸运的是,您不必将其翻译成 Javascript,因为在...
上有一个很好的资源可以实现这些“大地测量功能”
为了帮助您开始使用这些功能,这里有一个使用一些功能的快速示例...
- 计算两个坐标之间的距离,
- 计算给定起始坐标、方位角和距离(以米为单位)的坐标。 (此示例中使用的值取自上面标题为“给定距离和距起点的距离和方位角的目的地点”部分中的第一个参考资料。)
<script type="module">
import LatLon, { Dms } from 'https://cdn.jsdelivr.net/npm/geodesy@latest/latlon-spherical.min.js';
Dms.separator = '';
// Calculate distance between two points.
let d = new LatLon( 52.205, 0.119 ).distanceTo( new LatLon( 48.857, 2.351 ) );
console.log( d );
// Calculate destination point given starting point, distance and bearing.
let p1 = new LatLon( Dms.parse( "053°19′14″" ), Dms.parse( "-001°43′47″" ) );
let brng = Dms.parse( "096°01′18″" );
let dist = 124.8 * 1000; // Distance in meters.
let p2 = p1.destinationPoint( dist, brng ); // 53°11′18″N, 000°08′00″E
console.log( p2 );
console.log( [ Dms.toDms( p2.lat, 'dms' ), Dms.toDms( p2.lon, 'dms' ) ] );
</script>
虽然您最好按照 Jon Trent 的建议使用大地测量库,但快速修改可能会改进您的代码。
这是为了利用这样一个事实,即在给定的纬度下,经度的值与纬度的 cos(latitude) 一样多。所以在赤道上它们大致相同,但是当你接近两极时,经度的距离会越来越小。
如果您的边界框很小(比如几英里),您可以通过使用中间纬度的余弦作为比例因子来获得合理的精度。
正在引用...
...特别是 Destination point given distance and bearing from start point 部分下的 Javascript 代码,以下是计算给定目的地点的公式地球的纬度、经度、方位、距离和半径。请注意,角度以弧度为单位,距离以所需的任何单位(即英里、公里、米等)为单位。
此外,我还创建了两个辅助函数,用于将度、分和秒转换为弧度,反之亦然。可能不是最好的界面,但它们很实用。
//
// Convert degrees, minutes, and seconds to radians.
//
function dmsToRad( deg, min, sec ) {
return Math.sign( deg ) * ( Math.abs( deg ) + min / 60 + sec / 3600 ) * Math.PI / 180;
}
//
// Convert radians to degrees, minutes, and seconds. Rounds to 1/3600th.
//
function radToDms( rad ) {
let d = Math.round( Math.abs( rad ) * 180 / Math.PI * 3600 ) / 3600;
let deg = Math.trunc( d );
let remainder = ( d - deg ) * 60;
let min = Math.trunc( remainder );
remainder = ( remainder - min ) * 60;
let sec = Math.round( remainder );
deg = deg * Math.sign( rad );
return {
degrees: deg, minutes: min, seconds: sec,
pretty: `${(Math.sign(rad)===-1?'-':'')+('00'+Math.abs(deg)).slice(-3)}°${('0'+min).slice(-2)}′${('0'+sec).slice(-2)}″`
};
}
// Calculate the destination point given the latitude, longitude, bearing,
// distance, and radius of earth. Note that angles are in radians, and
// the distances are in whatever units desired (ie, miles, km, meters, etc).
//
// Sourced directly from https://www.movable-type.co.uk/scripts/latlong.html
//
function destinationPoint( φ1, λ1, brng, d, R ) {
const φ2 = Math.asin( Math.sin( φ1 ) * Math.cos( d / R ) +
Math.cos( φ1 ) * Math.sin( d / R ) * Math.cos( brng ) );
const λ2 = λ1 + Math.atan2( Math.sin( brng )*Math.sin( d / R ) * Math.cos( φ1 ),
Math.cos( d / R ) - Math.sin( φ1 ) * Math.sin( φ2 ) );
return { lat: φ2, lon: λ2 };
}
//
// Test using the sample data from the reference web site.
//
let dest = destinationPoint( dmsToRad( 53, 19, 14 ), dmsToRad( -1, 43, 47 ), dmsToRad( 96, 1, 18 ), 124.8, 6371 );
console.log( dest )
console.log( [ radToDms( dest.lat ), radToDms( dest.lon ) ] ); // 053°11′18″ 000°08′00″
注意方位为顺时针方向,北为0°。这些功能不仅可以让您计算 N、E、S 和 W 的目的地,还可以添加中间点,例如,如果您希望显示更圆的边界范围。
我有一小段代码用于查找某个位置的边界框,以便在半径范围内搜索用户个人资料。它几乎按照我的意愿工作,但不幸的是最终值有点失真。例如,如果我将 50 英里传递到函数中,我得到的点可能距原点 70 英里而不是 50 英里。我认为这与我正在计算的常量之一略有偏差有关。
常量:
const RAD_CON = Math.PI / 180; // Radian conversion constant
const R_EARTH = 6378; // Radius of the earth in Km
const KM_PER_MILE = 1.609344; // Km/mile conversion constant
// I think this might be wrong
const METER_DEGREES = (1 / ((2 * Math.PI / 360) * R_EARTH)) / 1000; // Meters per degree lat/lng
违规代码:
_getBoundingCoords(location: Location, miles: number) {
const meters = miles * KM_PER_MILE * 1000;
const latNorth = location.lat + (meters * METER_DEGREES);
const latSouth = location.lat - (meters * METER_DEGREES);
const lngWest = location.lng + (meters * METER_DEGREES) / Math.cos(location.lat * RAD_CON);
const lngEast = location.lng - (meters * METER_DEGREES) / Math.cos(location.lat * RAD_CON);
return [latNorth, latSouth, lngWest, lngEast];
}
如果您想知道我是如何使用它的,我将使用返回值来为 mongodb 集合构建查询。查询到的数据格式为
{
lat: number;
lng: number;
}
查询:
const query = {
$and: [{
'location.lat': {
$lte: latNorth,
$gte: latSouth,
},
}, {
'location.lng': {
$lte: lngWest,
$gte: lngEast,
},
}],
};
有一个 lot 与计算纬度和经度坐标相关的数学,在...[=14 有非常详细的解释=]
幸运的是,您不必将其翻译成 Javascript,因为在...
上有一个很好的资源可以实现这些“大地测量功能”为了帮助您开始使用这些功能,这里有一个使用一些功能的快速示例...
- 计算两个坐标之间的距离,
- 计算给定起始坐标、方位角和距离(以米为单位)的坐标。 (此示例中使用的值取自上面标题为“给定距离和距起点的距离和方位角的目的地点”部分中的第一个参考资料。)
<script type="module">
import LatLon, { Dms } from 'https://cdn.jsdelivr.net/npm/geodesy@latest/latlon-spherical.min.js';
Dms.separator = '';
// Calculate distance between two points.
let d = new LatLon( 52.205, 0.119 ).distanceTo( new LatLon( 48.857, 2.351 ) );
console.log( d );
// Calculate destination point given starting point, distance and bearing.
let p1 = new LatLon( Dms.parse( "053°19′14″" ), Dms.parse( "-001°43′47″" ) );
let brng = Dms.parse( "096°01′18″" );
let dist = 124.8 * 1000; // Distance in meters.
let p2 = p1.destinationPoint( dist, brng ); // 53°11′18″N, 000°08′00″E
console.log( p2 );
console.log( [ Dms.toDms( p2.lat, 'dms' ), Dms.toDms( p2.lon, 'dms' ) ] );
</script>
虽然您最好按照 Jon Trent 的建议使用大地测量库,但快速修改可能会改进您的代码。
这是为了利用这样一个事实,即在给定的纬度下,经度的值与纬度的 cos(latitude) 一样多。所以在赤道上它们大致相同,但是当你接近两极时,经度的距离会越来越小。
如果您的边界框很小(比如几英里),您可以通过使用中间纬度的余弦作为比例因子来获得合理的精度。
正在引用...
...特别是 Destination point given distance and bearing from start point 部分下的 Javascript 代码,以下是计算给定目的地点的公式地球的纬度、经度、方位、距离和半径。请注意,角度以弧度为单位,距离以所需的任何单位(即英里、公里、米等)为单位。
此外,我还创建了两个辅助函数,用于将度、分和秒转换为弧度,反之亦然。可能不是最好的界面,但它们很实用。
//
// Convert degrees, minutes, and seconds to radians.
//
function dmsToRad( deg, min, sec ) {
return Math.sign( deg ) * ( Math.abs( deg ) + min / 60 + sec / 3600 ) * Math.PI / 180;
}
//
// Convert radians to degrees, minutes, and seconds. Rounds to 1/3600th.
//
function radToDms( rad ) {
let d = Math.round( Math.abs( rad ) * 180 / Math.PI * 3600 ) / 3600;
let deg = Math.trunc( d );
let remainder = ( d - deg ) * 60;
let min = Math.trunc( remainder );
remainder = ( remainder - min ) * 60;
let sec = Math.round( remainder );
deg = deg * Math.sign( rad );
return {
degrees: deg, minutes: min, seconds: sec,
pretty: `${(Math.sign(rad)===-1?'-':'')+('00'+Math.abs(deg)).slice(-3)}°${('0'+min).slice(-2)}′${('0'+sec).slice(-2)}″`
};
}
// Calculate the destination point given the latitude, longitude, bearing,
// distance, and radius of earth. Note that angles are in radians, and
// the distances are in whatever units desired (ie, miles, km, meters, etc).
//
// Sourced directly from https://www.movable-type.co.uk/scripts/latlong.html
//
function destinationPoint( φ1, λ1, brng, d, R ) {
const φ2 = Math.asin( Math.sin( φ1 ) * Math.cos( d / R ) +
Math.cos( φ1 ) * Math.sin( d / R ) * Math.cos( brng ) );
const λ2 = λ1 + Math.atan2( Math.sin( brng )*Math.sin( d / R ) * Math.cos( φ1 ),
Math.cos( d / R ) - Math.sin( φ1 ) * Math.sin( φ2 ) );
return { lat: φ2, lon: λ2 };
}
//
// Test using the sample data from the reference web site.
//
let dest = destinationPoint( dmsToRad( 53, 19, 14 ), dmsToRad( -1, 43, 47 ), dmsToRad( 96, 1, 18 ), 124.8, 6371 );
console.log( dest )
console.log( [ radToDms( dest.lat ), radToDms( dest.lon ) ] ); // 053°11′18″ 000°08′00″
注意方位为顺时针方向,北为0°。这些功能不仅可以让您计算 N、E、S 和 W 的目的地,还可以添加中间点,例如,如果您希望显示更圆的边界范围。