先别急着打开地图 App,这事儿用 Go 解决更酷
你肯定有过这种瞬间:周末下午手痒得不行,想打两局乒乓球,结果手机里翻半天“离我最近的乒乓球馆”,跳出来的全是健身房广告,咱就是说,能不能用代码自己搞定这事儿?还真能,我最近用 Golang 写了个小工具,专门回答这个灵魂拷问,今天就把思路和代码拆给你看,保你学完能自己动手改造。
第一步:搞清楚“离我最近”到底咋算
地理坐标不是直线距离那么简单
你说“离我最近”,如果只看经纬度直线距离,那可就太天真了,北京东三环到西四环直线可能就 15 公里,但开车得绕 25 公里,咱得用球面距离公式,再结合道路实际路径,但话又说回来,找乒乓球馆这种“最后一公里”的活儿,直线距离也够用了,毕竟球馆通常都在居民区或者体育中心附近。
Golang 里有个很轻量的库叫 geolib,或者直接自己写个 Haversine 公式,十来行代码就搞定:
func haversine(lat1, lon1, lat2, lon2 float64) float64 {
const R = 6371 // 地球半径,公里
dLat := (lat2 - lat1) * math.Pi / 180
dLon := (lon2 - lon1) * math.Pi / 180
a := math.Sin(dLat/2)*math.Sin(dLat/2) +
math.Cos(lat1*math.Pi/180)*math.Cos(lat2*math.Pi/180)*
math.Sin(dLon/2)*math.Sin(dLon/2)
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
return R * c
}
第二步:数据从哪来?别傻乎乎爬高德
免费 API 和公开数据源,够用了
你可能会说“我要实时数据”,兄弟,别贪心,咱把问题切成两步:先拿到附近 3 公里内的球馆列表,再排序,数据源可以用以下三个:
| 数据源 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 高德地图 Web API | 数据全,有评分 | 每日免费额度有限 | 个人高频查询够呛 |
| 百度地图 POI 搜索 | 同样全,但返回字段稍杂 | 需要申请 AK | 做原型演示很香 |
| OpenStreetMap (Overpass) | 完全免费,无限制 | 国内数据稀疏 | 练手首选,可离线 |
我个人建议先用 Overpass 练手,因为免费且不用注册,查询语句也简单:
[out:json]; node["sport"="table_tennis"](around:3000,39.9042,116.4074); out;
这行查询的意思就是:以北京天安门为中心,半径 3000 米内所有带“table_tennis”标签的节点。
第三步:Golang 代码实战——从零搭个“球馆雷达”
核心逻辑:并发请求 + 排序输出
咱要写的东西不复杂,但有一点得讲究——并发去查多个数据源,不然一个请求卡你 5 秒,黄花菜都凉了,思路是这样:
- 拿到你的当前经纬度(可以用
ip定位,或者手机 GPS 传入)。 - 同时向 Overpass 和高德发请求(带上你自己的坐标)。
- 把两个结果合并成
[]Venue结构体,用 Haversine 算距离。 - 按距离升序排序,输出前 5 个。
结构体定义很关键,得留足字段,后面你要加“营业时间”“价格”也方便:
type Venue struct {
Name string `json:"name"`
Lat float64 `json:"lat"`
Lon float64 `json:"lon"`
Distance float64 `json:"distance"`
Source string `json:"source"`
Rating float64 `json:"rating,omitempty"`
}
并发我用 sync.WaitGroup,这玩意儿简单粗暴,别整太花哨的 channel 体操——咱是来解决问题的,不是来炫技的。
完整可运行的主逻辑框架
func main() {
myLat, myLon := 39.9042, 116.4074 // 测试坐标,替换成你自己的
venues := make([]Venue, 0)
var wg sync.WaitGroup
var mu sync.Mutex
// 并行查三个数据源
for _, src := range []string{"overpass", "amap", "osm_nominatim"} {
wg.Add(1)
go func(source string) {
defer wg.Done()
vs, err := fetchVenuesFrom(source, myLat, myLon)
if err != nil {
log.Printf("[%s] 查询失败: %v", source, err)
return
}
mu.Lock()
venues = append(venues, vs...)
mu.Unlock()
}(src)
}
wg.Wait()
// 算距离 + 排序
for i := range venues {
venues[i].Distance = haversine(myLat, myLon, venues[i].Lat, venues[i].Lon)
}
sort.Slice(venues, func(i, j int) bool {
return venues[i].Distance < venues[j].Distance
})
// 输出前 5 个
for i := 0; i < 5 && i < len(venues); i++ {
fmt.Printf("%d. %s(距离 %.2f 公里,来自%s)\n",
i+1, venues[i].Name, venues[i].Distance, venues[i].Source)
}
}
看到没,整个流程半小时能跑通,你要想再讲究点,可以把结果写进 SQLite 缓存,下次直接读本地,省得老调人家 API。

第四步:跑起来之后,你得注意的几个坑
坐标偏移问题(WGS-84 和 GCJ-02)
国内用的高德、百度,经纬度都是加过偏移的(GCJ-02 或 BD-09),而 Overpass 里是纯 WGS-84,你要是直接混着算距离,偏差能到 500 米以上,解决方法很简单:写个转换函数,或者统一都转成 WGS-84 再算。
“乒乓球馆”搜出来的是健身房
搜索关键词里带“乒乓球”,结果可能是一堆综合体育馆,里面确实有球台,但人家主要卖的是健身房年卡,你得多加一个过滤条件:name 字段里含“乒乓”或“球馆”,或者看 sport 标签是不是 table_tennis。
距离近不等于能打上
你搜到楼下 200 米有个球馆,结果人家是会员制,不接待散客。这个信息在 API 里往往不返回,得靠用户评论,我给个小建议:把高德里“评分>4分”且“人均<50元”的优先排前面,虽然不完美,但至少大概率靠谱。
写在最后的话
其实吧,写这个工具的过程中,我最大感受是:找球馆这事儿,本质上是个信息筛选问题,你不需要最全的数据库,你需要的是一套能自己维护、能按自己习惯排序的“雷达”,Golang 的并发特性加上免费数据源,完全能搞定。
你要是真按我这套代码跑通了,欢迎你来我常去的那个球馆切磋两局——就在东三环,离亮马桥地铁站 300 米,楼下有家特别好吃的面馆,不过地址嘛,你自己用代码搜去吧,这样才够味儿,对吧?
本文来自作者[kyadmin]投稿,不代表678体育 - 全网热门体育赛事高清直播平台立场,如若转载,请注明出处:http://www.66weibo.cn/ly/1721.html
评论列表(4条)
我是678体育 - 全网热门体育赛事高清直播平台的签约作者“kyadmin”!
希望本篇文章《离我最近的乒乓球馆在哪里?用 Golang 写个球馆雷达自己找》能对你有所帮助!
本站[678体育 - 全网热门体育赛事高清直播平台]内容主要涵盖:678体育,678体育官网,678赛事直播
本文概览:先别急着打开地图App,这事儿用Go解决更酷你肯定有过这种瞬间:周末下午手痒得不行,想打两局乒乓球,结果手机里翻半天“离我最近...