You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用PHP筛选地图指定半径范围内的指定数量坐标点

经纬度半径范围点位筛选实现方案

你当前全表查询返回所有点位的逻辑无法满足半径筛选需求,直接在SQL层通过Haversine球面距离公式做筛选即可,3000条数据量下性能完全够用,还支持直接限制返回点位数量,最终效果就是指定圆形范围内的点位被保留、范围外点位被排除,和你给出的参考示例逻辑一致。

实现逻辑

  • 先做矩形粗筛:根据给定半径先算出经纬度的大致上下界,过滤掉明显不在范围内的点,减少后续计算量
    • 1纬度对应的地表距离约为111km,因此纬度筛选范围为中心点纬度 ± (半径公里数/111)
    • 经度每度对应的距离随纬度升高而缩短,计算公式为111 * cos(中心点纬度弧度值),因此经度筛选范围为中心点经度 ± (半径公里数/上述计算值)
  • 再做球面距离精算:用Haversine公式计算粗筛后每个点位和中心点的实际地表距离,只保留距离小于等于指定半径的点
  • 最后按距离从近到远排序,限制返回指定数量的点位即可

改造后的代码

替换你原来的全表查询逻辑即可,代码中用了预处理语句避免SQL注入问题:

<?php
require_once("db.php");

function parseToXML($htmlStr)
{
$xmlStr=str_replace('<','&lt;',$htmlStr);
$xmlStr=str_replace('>','&gt;',$xmlStr);
$xmlStr=str_replace('"','&quot;',$xmlStr);
$xmlStr=str_replace("'",'&#39;',$xmlStr);
$xmlStr=str_replace("&",'&amp;',$xmlStr);
return $xmlStr;
}

// 筛选参数,可根据业务场景从GET/POST参数中获取
$centerLat = $_GET['lat'] ?? 0; // 中心点纬度
$centerLng = $_GET['lng'] ?? 0; // 中心点经度
$radiusKm = $_GET['radius'] ?? 5; // 筛选半径,单位公里,默认5公里
$limit = $_GET['limit'] ?? 100; // 返回点位数量,默认100个

// 参数合法性校验
if (!is_numeric($centerLat) || !is_numeric($centerLng) || !is_numeric($radiusKm) || !is_numeric($limit)) {
    die('Invalid parameter');
}
$centerLat = floatval($centerLat);
$centerLng = floatval($centerLng);
$radiusKm = floatval($radiusKm);
$limit = intval($limit);

// 计算粗筛经纬度边界
$latRange = $radiusKm / 111;
$minLat = $centerLat - $latRange;
$maxLat = $centerLat + $latRange;
$lngRange = $radiusKm / (111 * cos(deg2rad($centerLat)));
$minLng = $centerLng - $lngRange;
$maxLng = $centerLng + $lngRange;

// Haversine公式计算距离的SQL片段
$distanceSql = "
    6371 * ACOS(
        LEAST(1, COS(RADIANS(?)) * COS(RADIANS(lat)) * COS(RADIANS(lng) - RADIANS(?)) + 
        SIN(RADIANS(?)) * SIN(RADIANS(lat)))
    ) AS distance
";

// 预处理查询语句,先粗筛再精算,排序后限制数量
$query = "
    SELECT id, address, lat, lng, $distanceSql
    FROM addresses
    WHERE lat BETWEEN ? AND ?
      AND lng BETWEEN ? AND ?
    HAVING distance <= ?
    ORDER BY distance ASC
    LIMIT ?
";
$stmt = mysqli_prepare($conn, $query);
mysqli_stmt_bind_param($stmt, 'ddddddddi', $centerLat, $centerLng, $centerLat, $minLat, $maxLat, $minLng, $maxLng, $radiusKm, $limit);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);

if (!$result) {
  die('Invalid query: ' . mysqli_error($conn));
}

header("Content-type: text/xml");

// 输出XML
echo "<?xml version='1.0' ?>";
echo '<markers>';
$ind=0;
while ($row = mysqli_fetch_assoc($result)){
  echo '<marker ';
  echo 'id="' . $row['id'] . '" ';
  echo 'name="' . $row['id'] . '" ';
  echo 'address="' . parseToXML($row['address']) . '" ';
  echo 'lat="' . $row['lat'] . '" ';
  echo 'lng="' . $row['lng'] . '" ';
  echo 'distance="' . round($row['distance'], 2) . '" '; // 可选输出点位到中心点的距离,单位公里
  echo 'type="House" ';
  echo '/>';
  $ind++;
}

echo '</markers>';

mysqli_stmt_close($stmt);
mysqli_close($conn);
?>

注意点

  • 代码里的6371是地球半径,单位为公里,如果需要用英里做单位,把这个值改成3959即可
  • LEAST(1, ...)是为了避免浮点计算误差导致ACOS参数超过1抛出数据库错误
  • 你当前3000条的数据量,即使去掉粗筛逻辑直接全表计算距离也不会有明显性能问题,加矩形粗筛是为了后续数据量增长到几万、几十万级别的时候依然能保持查询效率
  • 如果后续数据量超过10万条,建议给lat、lng字段加普通索引,或者升级使用MySQL空间索引做地理位置查询,性能会提升几个量级
  • 如果你使用的是MySQL 5.7.6及以上版本,也可以用内置的空间函数ST_Distance_Sphere替代手写Haversine公式,写法更简洁,性能也更好,对应距离计算SQL片段如下,注意POINT参数顺序为经度在前、纬度在后,计算结果默认单位为米,除以1000即可转为公里:
ST_Distance_Sphere(POINT(lng, lat), POINT(?, ?)) / 1000 AS distance

内容的提问来源于stack exchange,提问作者m0nsterr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 23:57:19