做了一个把走路面积量化的 iOS App,25m 网格精度下踩了不少坑

4 阅读5分钟

每次 citywalk 完看一眼轨迹线就关了,总觉得缺点什么

这个感觉困扰了我挺久。跑步 App、骑行 App 都会记轨迹,但看完那条线之后呢?没了。一条线给不了什么成就感,看过就忘。

后来我想明白了:线是一维的,但你实际「占领」的地面是二维的。如果把轨迹线撑开成面积,告诉你「今天你踩过了 0.3 km² 的土地」,这个数字就有积累感了。每天加一点,像在地图上慢慢涂色。

于是我做了「雁过留痕」这个 iOS App,核心就一件事:把 GPS 轨迹转化成探索面积(km²),让每一步都有可量化的回报。目前 App Store 评分 5 分,样本量还小,但留下来的用户粘性很不错。

面积计算:为什么是 25m 网格

技术上我用了一套网格体系——把地图切成 25m × 25m 的格子,GPS 坐标落入哪个格子,那个格子就算被「点亮」。最终面积 = 点亮格子数 × 625m²。

为什么是 25m?这个数字是试出来的:

  • 10m:数据膨胀太快,一个用户走一天能产生几万个格子记录,手机上跑起来明显卡顿
  • 50m:走一条窄巷子可能一个格子都没点亮,用户心里觉得「白走了」
  • 25m:一天 citywalk 大概解锁 0.2 - 0.5 km²,数字不大不小,有增长感但不会一下刷满

中国行政区匹配——比想象中坑多

做省市解锁玩法,你得有边界数据,还得处理坐标系偏移。

边界数据我最终用的是阿里云 DataV 的 GeoJSON(datav.aliyun.com/portal/school/atlas),精度够用且免费。但原始数据一个省的多边形动辄上千个点,34 个省加起来好几十 MB,手机上冷启动加载直接卡死。

简化用的是 Douglas-Peucker 算法,tolerance 设在 0.005°(大概 500m 级别)。最后每个省压缩到几百个点,总文件从 40MB 降到了 2MB 左右。代价是海岸线和省界的锯齿明显了,但对于「判定用户在哪个省」这个需求,完全够用。

坐标系是另一个坑。iOS 上 CoreLocation 给的是 WGS-84,但中国地图显示和行政区数据用的是 GCJ-02。如果不做转换,用户站在北京二环内可能被判定到河北。我内置了 WGS-84 → GCJ-02 的转换,在做 point-in-polygon 判定之前先把坐标对齐。

成就系统设计

这块花的时间比写代码还多。我做了一个多维度的徽章体系,所有判定逻辑的输入源是一个 BadgeMetrics 结构:

struct BadgeMetrics {
    let totalDistanceKilometers: Double
    let recordedDays: Int
    let currentStreakDays: Int
    let longestStreakDays: Int
    let nightSegmentCount: Int
    let chinaProvinceCount: Int
    let chinaAreaKm2: Double
    let cityUnlockCount: Int
    let globalAreaKm2: Double
    // ...
}

徽章分成五条线路(BadgeTrack):exploration(探索距离)、consistency(连续打卡)、china(省市解锁)、world(全球覆盖)、pro(会员专属)。每条线路下面有多个系列,每个系列有阶梯式的里程碑。

enum BadgeTrack: String, CaseIterable {
    case exploration
    case consistency
    case china
    case world
    case pro
}

最早我只做了两类——距离和打卡——但测试时发现用户对「解锁新省份」这件事兴奋程度远超我预期。有个测试用户出差回来,第一件事不是看自己走了多少公里,而是看有没有点亮新的省。所以我把地理解锁单独拆了出来,做了中国和全球两条线。

设计上踩的最大坑是:阶梯之间的间距。最早我设的是 1 天、7 天、30 天、100 天、365 天。结果 30 天到 100 天这个跨度太大了,用户到了 40 多天开始掉链子,因为看不到下一个目标。后来我在中间加了 60 天档位,留存数据好了一些。

后台 GPS 记录的取舍

后台持续定位,电量消耗是永远的话题。

我的默认配置是 desiredAccuracy = kCLLocationAccuracyBestdistanceFilter = 10m。有用户反馈一天 citywalk 下来掉了 15% 电。对于真想记录的人这可以接受,但还是加了一个「省电模式」——精度降到 kCLLocationAccuracyNearestTenMeters,distanceFilter 加大到 25m,耗电大概砍掉一半。

轨迹自动分段用的是静止检测:连续 5 分钟没有超出 distanceFilter 的位移,就自动截断 segment。到了目的地坐下来,segment 自然结束。

有个细节——少于 8 个点的 segment 直接丢弃。这是为了过滤 GPS 漂移产生的「幽灵轨迹」,你在室内坐着不动,GPS 偶尔跳几下产生 3-5 个点,这种不应该计入数据。

新用户留存的问题

说实话目前下载量不大。我觉得根本原因是这类 App 的价值需要用一段时间才能体会到,截图很难传达那种「面积慢慢增长」的感觉。

数据上看到一个现象:Day1 留存偏低,但 Day7 留存反而还行。说明留下来的人是真觉得有用,但第一天的体验不够打动人。

我的猜测是第一天面积数据太少,成就感不够。后来加了「新手首次记录奖励」——第一次完成记录直接给一个徽章——Day1 数据稍微好了一点,但还在继续调。

写在最后

做地图类 App 的坑比想象中密集。坐标系转换、后台定位权限的系统弹窗策略、不同 iOS 版本对 Background Location 的限制差异、行政区边界数据的获取和简化……每一个单独拎出来都能写一篇。

对了,如果你也在做需要后台定位的 App,iOS 17 之后 CLBackgroundActivitySession 的行为有变化,建议去翻一下 WWDC23 的 session 10180,能省不少踩坑时间。

你们做地图类或者定位相关的 App 时还踩过什么坑?评论区聊聊。