小i约球诞生记02:做附近球友匹配时,LBS 能力怎么选 本文是「小i约球诞生记」系列文章之一。小i约球是一个微信小程序主要帮乒乓球、羽毛球、台球、网球爱好者找到附近合适的球友。本文从一个具体技术或产品问题切入记录约球小程序从 0 到 1 过程中的设计取舍。前言做“附近球友”这个功能时我一开始以为 LBS 很简单。拿到用户经纬度查附近的人按距离排一下就结束了。但真正放到约球场景里会发现 LBS 不是一个单点能力而是一组能力的组合小程序怎么拿定位用户怎么选择常用约球地点数据库存什么坐标格式附近查询交给谁做地图怎么展示点位列表和地图是不是同一套数据距离、球类、场景筛选怎么组合可选方案做这个功能时大概有几种方案。方案一只存文本地址最简单的方式是只让用户填写地址比如“海淀区西二旗附近”“某某羽毛球馆”。优点是实现非常简单。缺点也很明显不能准确算距离不能做地图点位不能按半径查询地址文本不统一后期很难清洗同一个地点可能有很多种写法这个方案只适合非常早期的人工运营不适合真正做附近匹配。方案二前端拿经纬度后端普通字段存储第二种方式是用小程序拿到经纬度然后存在普通字段里{latitude:39.9,longitude:116.3}这个方案比纯文本好很多至少可以自己算距离也可以在地图上显示点位。但如果后端只是普通字段附近查询就比较麻烦。你要么全量拉出来自己算要么自己做经纬度范围过滤再二次计算距离。用户量小的时候能跑数据量稍微大一点就会很难受。方案三使用云开发数据库 Geo 能力小i约球最后选择的是这个方案。用户选点或定位后普通地址信息继续保留同时额外存一个数据库 GeoPointconstlocationdb.Geo.Point(longitude,latitude)这样数据库里同时有两类字段address用于展示地点名称、详细地址、原始经纬度location用于数据库地理位置查询这种设计的好处是比较均衡。展示层可以拿address查询层可以拿location。前端不用自己全量算距离后端也不用额外接一个地图服务。对一个微信小程序 云开发项目来说这是最顺手的方案。为什么没有一开始接外部地图服务外部地图服务当然也能做 LBS比如地点检索、路线规划、行政区划、POI、逆地址解析等。但小i约球第一阶段没有把它作为核心依赖。原因是第一阶段需要解决的问题并不复杂用户选择一个约球地点系统存下坐标查询附近球友地图展示点位这些能力小程序和云开发已经能覆盖。如果一开始就接外部地图服务会多出不少成本key 管理请求配额域名配置坐标系处理接口封装失败兜底所以我的选择是第一阶段尽量用微信生态已有能力等需要更复杂的 POI、路线、行政区、地理围栏时再考虑接入外部地图服务。小程序端定位和选点是两件事这里是很容易混淆的地方。小程序里至少有两个和位置相关的能力getLocationchooseLocation它们解决的问题不一样。getLocation解决的是“用户现在在哪里”。比如首页打开时可以拿当前定位作为默认中心点用来展示附近球友。chooseLocation解决的是“用户想把约球名片放在哪里”。这个位置不一定是当前定位。一个用户可能现在在公司但想发布家附近的羽毛球约球名片也可能人在家里但想发布常去球馆的乒乓球名片。所以在小i约球里这两个能力不能混用首页默认定位用getLocation发布约球名片用chooseLocation这是产品场景决定的不只是技术接口差异。小程序权限定位不是无条件可用小程序定位能力还涉及权限配置。在app.json里需要声明相关用途比如{permission:{scope.userLocation:{desc:用于查找附近球友和球馆}},requiredPrivateInfos:[chooseLocation,getLocation]}这里有两个现实问题。第一用户可能拒绝授权。所以首页不能把定位当成绝对前提。定位失败时要么用历史位置要么展示默认城市或默认数据要么引导用户开启定位。第二不同定位能力的审核和精度不一样。低精度定位获取门槛低但偏差可能比较大。对资讯类应用影响不大但对约球这种线下场景偏差几公里就很明显。这里的重点是定位能力不能只看“能不能拿到”还要看精度是否符合线下见面的场景。云开发 Geo 能力适合做什么云开发数据库的 Geo 能力最适合做“给定一个点查附近的数据”。在小i约球里它主要用在三个场景。1. 查附近球友名片用户打开首页时根据用户位置查附近的约球名片。这里除了地理位置还要叠加业务条件不看自己的名片按球类筛选过滤无效数据只查普通约球名片这说明真实查询不会只有geoNear一定是“地理条件 业务条件”的组合。2. 发布名片时找潜在匹配对象用户发布新名片后也可以反向查询附近已有球友。这和首页查询方向不同。首页查询是我在哪里附近有什么。发布匹配是我发了一个新点附近谁可能对它感兴趣。同样是 Geo 查询但产品含义不同。3. 查附近本地群本地群也可以看作一种地理对象。一个群如果对应某个街道、球馆或区域就可以给它存一个位置。用户查附近群时也可以用 Geo 能力做半径查询。这说明位置能力最好设计成底层能力而不是只服务某一个页面。小程序 map 组件能做什么小程序自带map组件已经能满足第一阶段需求。最核心的几个能力是设置中心点longitude/latitude设置缩放级别scale传入markers监听 marker 点击监听地图视野变化小i约球首页地图大概就是这种结构maplongitude{{user.location.longitude || 116.397428}}latitude{{user.location.latitude || 39.90923}}markers{{allMarkers}}bindmarkertaponMarkerTapbindregionchangeonRegionChangescale13/这已经能完成基础的附近球友地图用户当前位置作为中心点附近球友作为 marker点击 marker 进入详情缩放地图时调整 marker 展示方式对第一阶段来说不需要自己接一套 Web 地图库。map 组件的限制不过小程序map组件也不是没有限制。最典型的是 marker 展示。普通 marker 只能传图标、位置、大小等属性。如果你想实现更复杂的效果比如用用户头像作为 marker不同球类不同图标教练、陪练、球馆、群聊带不同角标缩放级别高时显示角标缩放级别低时隐藏角标就需要自己做更多处理。坐标系和精度问题小程序里常见坐标类型是gcj02。如果你的数据全部来自微信小程序的定位和选点内部保持一致就好。真正容易出问题的是这些情况外部地图服务返回的是另一种坐标系后台导入的数据来自不同来源用户定位是低精度地址文本和经纬度不一致小i约球第一阶段尽量避免引入多套坐标来源就是为了降低这类问题。但即便如此定位精度仍然会影响体验。低精度定位可能偏几公里这对“附近球友”来说不是小问题。这个问题会在后面的 Location 接口踩坑里展开。半径怎么选附近查询一定要面对一个问题多远算附近这个值不是纯技术问题。对线下运动来说半径和城市密度、运动类型、用户习惯都有关系。比如乒乓球可能更依赖固定球馆羽毛球对场馆和时间更敏感台球用户可能愿意走远一点一线城市直线 5km 和实际通勤 5km 不是一回事第一阶段可以先用一个相对保守的范围比如 10km。它不一定完美但足够验证产品闭环。后面更合理的方式是把半径做成可配置用户自己选择活动范围不同城市使用不同默认半径不同球类使用不同默认半径根据数据密度动态扩大或缩小范围总结LBS 选型不能脱离业务场景。如果只是做一个地图展示可能一个map组件就够了。如果只是存一个地址文本字段也能应付。但如果要做“附近球友匹配”就必须把定位、选点、Geo 存储、附近查询、地图点位和权限降级一起考虑。小i约球第一阶段选择了微信小程序能力 云开发 Geo 能力 小程序 map 组件。这套方案的核心价值不是最强而是足够贴合早期产品成本低、集成顺、验证快。本文属于「小i约球诞生记」系列。