每次 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 = kCLLocationAccuracyBest,distanceFilter = 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 时还踩过什么坑?评论区聊聊。