需求来源于生活,每到周末都会去游泳,出行工具基本上都是公交。要做的事情很简单,需要一个卡着公交到站点的时间到达公交站。目前已经有很多第三方的公交查询软件,之所以要自己造轮子的原因是我觉得这些第三方目前做的不够成熟,没有我需要的数据,比如一天中公交到站的时间点有哪些。查询公交只是一个基础服务,我要做的是能够根据自己的生活规律和时间规划完成一个全自动提醒出行的方案,实现历史到站统计和数据分析的功能。
等公交的过程就像在浪费生命,如何使得等公交变得有意义是一个值得思考的问题🤔
故事背景
有一次我在公交站等了将近30分钟的公交车,还不见发车,当时的想法是如果早知道
不发车我就选择打车,或者晚点出门做一些有意义的事了。但是我要如何才能实现早知道
?
周末出行的时间不固定,和朋友约好的时间也不固定,想要卡着时间点坐上公交,可以自己花几个周末一天啥事都不干,就盯着公交到站,然后记录一下时间,下次根据这个时间选择合理的出门时间。但是这样太蠢了!
既然目前已经有这么多公交查询软件,何不写个程序每时每刻都自动获取公交到站情况并且记录下来呢?说干就干。
目前的第三方公交查询服务有很多,我目前所在的城市(厦门市)比较权威的公交查询主要是
掌上公交
App。
抓包->分析->构造请求(这些日常操作这里就省略了)
基础功能
掌上公交
在厦门市目前是一个比较精准的公交查询数据服务,通过分析发现不仅提供了公交站点查询、公交信息查询,这些信息里面还包含了实时的公交GPS的经纬度,通过位置可以计算出公交距离目标站点的距离。
通过掌上公交的数据接口要实现以下两个基础功能
- [公交站点信息] 查询指定路线上的所有公交站点信息
- [公交实时信息] 查询指定路线当前即将到站的所有公交实时信息
公交站点信息
查询指定路线上的所有公交站点信息。
程序代码
首先根据接口请求响应的json数据,编写映射实体
bus_station_api.go1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39// 站点数据
type StationData struct {
StationId int `json:"stationId"` // 站点id
StationName string `json:"stationName"` // 站点名称
StationLon float64 `json:"station_lon"` // 经度
StationLat float64 `json:"station_lat"` // 纬度
StationOrder int `json:"stationOrder"` // 站点序号
ShowName string `json:"showName"` // 显示名称
Come int `json:"come"` // 到达状态
Arrive int `json:"arrive"` // 驾驶状态
}
// 站点信息
type StationInfo struct {
RouteName string `json:"routeName"` // 公交路线名称
UpperOrDown string `json:"upperOrDown"` // 正向1 逆向2
BeginTime string `json:"beginTime"` // 首班车时间
EndTime string `json:"endTime"` // 末班车时间
PlanTime string `json:"planTime"` // 计划发车时间
Common string `json:"commonts"` // 路线描述
Data []StationData `json:"data"` // 站点数据
}
const StationInfoCmd = "103" // 查询站点信息操作码
// 获取路线上所有公交站点信息
func GetRouteAllStationInfo(requestURL string, cityName string, routeName string, direction string) *StationInfo {
response, _ := http.Post(requestURL, "application/x-www-form-urlencoded", strings.NewReader(
"CMD="+StationInfoCmd+
"&CITYNAME="+cityName+
"&LINENAME="+routeName+
"&DIRECTION="+direction+
""))
body, _ := ioutil.ReadAll(response.Body)
var resp StationInfo
_ = json.Unmarshal([]byte(string(body)), &resp)
log.Println("公交站点信息:" + string(body))
return &resp
}
测试代码
bus_station_api_test.go1
2
3
4
5
6
7
8
9
10
11
12func TestGetRouteAllStationInfo(t *testing.T) {
url := "https://wx.shenghuoquan.cn/WxBusServer/ApiData.do"
cityName := "厦门市"
routeName := "641路"
direction := "2"
stationInfo := GetRouteAllStationInfo(url, cityName, routeName, direction)
for i := 0; i < len(stationInfo.Data); i++ {
// 打印站点索引位置和站点名称
t.Log(stationInfo.Data[i].StationOrder, stationInfo.Data[i].StationName)
}
}
公交实时信息
查询指定路线当前即将到站的所有公交实时信息
程序代码
bus_info_api.go1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51// 主要数据
type InfoData struct {
StationName string `json:"stationName"` // 站点名称
Index int `json:"index"` // 站点在路线上的索引位置 从0开始
Arrive int `json:"arrive"` // 驾驶状态 0行驶中 1停止行驶
Come int `json:"come"` // 到站状态 0已到站 1即将到站
ShowType int `json:"showType"` // 显示类型
}
// 详细数据
type InfoList struct {
StationName string `json:"stationName"` // 站点名称
StatusType string `json:"statusType"` // 状态类别 0已到站 2即将到站
Index int `json:"index"` // 站点在路线上的索引位置 从0开始
StationLat float64 `json:"station_lat"` // 经度
StationLng float64 `json:"station_lng"` // 纬度
BusLat float64 `json:"bus_lat"` // 公交经度
BusLng float64 `json:"bus_lng"` // 公交纬度
BusNumber string `json:"busNumber"` // 公交车牌号码
CrowdedStatus string `json:"crowdedStatus"` // 拥挤状态
BusToStationNiheDistance float64 `json:"busToStationNiheDistance"` // 公交到站距离
NihePointIndex int `json:"nihePointIndex"` // 附近点指数
Angle float64 `json:"angle"` // 角度
RouteNumber string `json:"routeNumber"` // 路由号
UpperOrDown string `json:"upperOrDown"` // 正向或逆向
BusIcon string `json:"busIcon"` // 公交图标
RecTime int `json:"_recTime"` // 获取时间
}
// 公交信息
type Info struct {
Data []InfoData `json:"data"`
List []InfoList `json:"list"`
}
const busInfoCmd = "104" // 查询站点信息操作码
// 获取指定路线的公交实时信息
func GetRouteBusInfo(requestURL string, cityName string, routeName string, direction string) *Info {
response, _ := http.Post(requestURL, "application/x-www-form-urlencoded", strings.NewReader(
"CMD="+busInfoCmd+
"&CITYNAME="+cityName+
"&LINENAME="+routeName+
"&DIRECTION="+direction+
""))
body, _ := ioutil.ReadAll(response.Body)
var resp Info
_ = json.Unmarshal([]byte(string(body)), &resp)
log.Println("公交实时信息:" + string(body))
return &resp
}
测试代码
bus_info_api_test.go1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16func TestGetRouteBusInfo(t *testing.T) {
url := "https://wx.shenghuoquan.cn/WxBusServer/ApiData.do"
cityName := "厦门市"
routeName := "641路"
direction := "2"
busInfo := GetRouteBusInfo(url, cityName, routeName, direction)
for i := 0; i < len(busInfo.List); i++ {
switch busInfo.List[i].StatusType {
case "0":
t.Logf("公交站索引:%3d %s[即将到站]",busInfo.List[i].Index, busInfo.List[i].StationName)
case "2":
t.Logf("公交站索引:%3d %s[已经到站]",busInfo.List[i].Index, busInfo.List[i].StationName)
}
}
}
经过分析,查询公交和站点的基础的数据接口就只有这两个,通过分析可以发现掌上公交的一些基础数据处理都是在客户端来做的
掌上公交客户端的参数都是固定的(城市名称,路线名称,方向,请求操作码),客户端主要做了两件事
- 通过获取站点信息,得到每个站点的索引值
- 通过查询公交实时信息,得到公交目前所在的索引,客户端通过目标站点索引和公交站点索引计算出距离的站点
我认为这一部分也应该由后端来做,并且应该做得更加具体。掌上公交接口根据操作码提供了数据,与其说是一个接口服务,不如说他是一个更加偏向于数据的基础服务。我要做的就是整合不同的数据服务,实现一个公交中台服务。前台只需要数据请求和获取能力。
其中站点的数据是固定不变的,可以将站点数据存储到数据库中,为了每次查询速度更快,把站点数据也存到了redis中,实现前端根可以根据站点名称进行查询。具体代码就不贴了,
完整代码和Release版本在GitHub上:https://github.com/lanshiqin/bus_notify
扩展功能(未完待续)
- 统计指定路线的公交每天的到站时间点
- 根据用户出行速度推送最优的乘车时间点