这个用于地理定位搜索的 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 的目的地,还可以添加中间点,例如,如果您希望显示更圆的边界范围。