云端 Mac 上的 iOS 地理距离与围栏边界验收

云端 Mac 上的 iOS 地理距离与围栏边界验收

附近门店突然显示在数万千米之外,未必是定位服务失效。经纬度顺序写反、跨反经线直接相减、先把距离四舍五入再判断范围,都能制造类似故障。在 GNUMac 云端 Mac 上,可以先用固定坐标把计算层跑通,再把定位输入与设备回调接回来。这样失败时有明确归属,不必反复拖动地图猜原因。

先写清坐标与距离契约

本文验证“两个坐标之间的地表距离”,不是驾车路线长度,也不代表用户实际行走距离。输入采用纬度在前、经度在后的十进制度数;内部距离统一用米,界面显示时才转换单位。

纬度必须处于负九十度至正九十度,经度必须处于负一百八十度至正一百八十度,同时拒绝非有限数。不要把非法输入悄悄裁到边界,否则上游数据损坏会变成一条看似正常的记录。

坐标合法不等于定位可信。零度纬度、零度经度是合法位置,不能无条件当作“尚未定位”;是否接受它,应由数据来源契约决定。

地图上的屏幕距离也不能作为预期值。投影、缩放与设备像素都会改变画面,却不应改变业务计算。

在云端 Mac 跑固定样本

准备已选定开发工具链的 macOS 环境,执行 xcrun swift --version 确认编译器可用。将下面内容保存为 distance-check.swift。脚本不读取当前位置,不需要请求定位权限,也不访问网络。

import Foundation
import CoreLocation

enum CoordinateError: Error {
    case invalid
}

func point(_ latitude: Double, _ longitude: Double) throws -> CLLocation {
    let coordinate = CLLocationCoordinate2D(
        latitude: latitude,
        longitude: longitude
    )
    guard latitude.isFinite,
          longitude.isFinite,
          CLLocationCoordinate2DIsValid(coordinate) else {
        throw CoordinateError.invalid
    }
    return CLLocation(latitude: latitude, longitude: longitude)
}

func rejects(_ latitude: Double, _ longitude: Double) -> Bool {
    do {
        _ = try point(latitude, longitude)
        return false
    } catch {
        return true
    }
}

let origin = try point(0, 0)
precondition(origin.distance(from: origin) == 0)

let east = try point(0, 179.9)
let west = try point(0, -179.9)
let crossing = east.distance(from: west)
precondition(crossing > 20_000 && crossing < 23_000)
precondition(abs(crossing - west.distance(from: east)) < 1)

let polarA = try point(89.9, 0)
let polarB = try point(89.9, 180)
let polarDistance = polarA.distance(from: polarB)
precondition(polarDistance > 20_000 && polarDistance < 23_000)

precondition(rejects(91, 0))
precondition(rejects(0, 181))
precondition(rejects(.nan, 0))
precondition(rejects(0, .infinity))

运行命令如下。全部断言通过时没有输出,失败则返回非零状态,适合直接作为流水线步骤。

xcrun swift distance-check.swift

这组范围是测试夹具的宽容差,不是性能实测或业务精度承诺。反经线两侧的点相距很近;极区经度差很大,也不代表相距半个地球。两项都能抓住直接对经纬度做平面减法的错误。

把围栏边界独立成纯函数

假设业务规则是“距离小于或等于半径算进入”。先用人工距离测试比较器,再将坐标计算结果接入,避免同时调试两层问题。把下面代码追加到同一文件后重新执行。

func isInside(distance: Double, radius: Double) -> Bool {
    guard distance.isFinite, radius.isFinite,
          distance >= 0, radius >= 0 else {
        return false
    }
    return distance <= radius
}

precondition(isInside(distance: 499.9, radius: 500))
precondition(isInside(distance: 500, radius: 500))
precondition(!isInside(distance: 500.1, radius: 500))
precondition(!isInside(distance: .nan, radius: 500))
precondition(!isInside(distance: 0, radius: -1))

let meters = 1_250.0
precondition(meters / 1_000 == 1.25)

边界样本要包含恰好相等,而不只是明显在内、明显在外。不要用显示为“零点五千米”的字符串反推结果;不同原始距离可能被舍入成同一个文本。

如果产品要减少边缘反复切换,可以定义进入与退出使用不同阈值的滞回策略。那属于状态机,需要补测连续输入序列,不能在比较器里随意加一个“差不多”的常数。

定位精度不是计算容差

真实位置样本还带有时间与水平精度。接入层应明确拒绝过期样本,并处理负的水平精度值;有效但精度较差的坐标,也不应直接生成“确认进入”事件。

一种可审核的保守规则是:设距离为 d、水平精度为 a、半径为 r。只有 d+a≤r 才判为确定在内;只有 d−a>r 才判为确定在外,其余标记不确定。它是业务上的保守判断,不是把精度值解释成绝对误差保证。

测试时分别注入精确、模糊、过期和无效样本,核对不确定状态是否保留,以及界面是否错误显示确定结论。不要通过放宽计算断言来掩盖定位噪声。

留存证据并接回 iOS 工程

上述脚本适合快速复核框架行为。正式接入时,把坐标校验与比较器放进可测试模块,由测试目标直接调用生产实现;只维护一份算法,避免脚本正确而应用仍使用旧逻辑。

每次运行留存提交标识、工具链版本、夹具坐标、原始米值及失败断言。系统升级后先检查失败属于输入校验、距离范围还是状态策略,不要直接刷新全部预期值。

最后保留一轮 iOS 真机验收:区域监测事件、后台运行和实际移动属于另一条链路。纯计算测试通过,只说明固定输入得到了符合契约的结果。将这条边界写进验收纪要,比增加随机坐标更有价值。

常见问题

云端 Mac 上的脚本通过,是否代表 iOS 围栏功能已经验收完成?

不代表。脚本验证坐标校验、距离计算与边界策略;区域监测的回调、后台行为和实际定位误差仍需在 iOS 真机上单独验收。

围栏边界能否直接使用显示出来的千米数判断?

不能。业务判断应使用未格式化的米值,显示层再换算和舍入;边界容差还应与定位精度策略分开定义。

为你的工作流配置一台独享 Mac mini

按项目选择机型、节点与租期。先核对内存、存储和连接需求,再提交订单;实际可用状态以控制台实时返回为准。

先确定工作流,再把机器开起来。

独享物理 Mac mini。选择机型、节点与租期后,再按项目需要配置存储。