commit 41be1db3818712c68c19761abfc37bd869e3e1eb
| author | 斟酌 鵬兄 <tgckpg@gmail.com> |
| date | 2022-09-25T10:48:22Z |
| subject | Added kmb schedules |
commit 41be1db3818712c68c19761abfc37bd869e3e1eb
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date: 2022-09-25T10:48:22Z
Added kmb schedules
---
datasources/kmb/QueryResult.go | 144 +++++++++++++++++++++++++++++++++----
datasources/kmb/RouteStop.go | 29 ++++++--
datasources/kmb/query.go | 21 +++++-
datasources/kmb/query_test.go | 15 ++++
datasources/kmb/routestops.go | 20 +++---
datasources/kmb/routestops_test.go | 2 +-
datasources/kmb/schedules.go | 62 ++++++++++++++++
datasources/mtr/bus/QueryResult.go | 26 ++++---
8 files changed, 281 insertions(+), 38 deletions(-)
diff --git a/datasources/kmb/QueryResult.go b/datasources/kmb/QueryResult.go
index 8217bdd..3a9ee42 100644
--- a/datasources/kmb/QueryResult.go
+++ b/datasources/kmb/QueryResult.go
@@ -2,18 +2,52 @@ package kmb
import (
"fmt"
+ "sort"
"strings"
+ "time"
query "github.com/tgckpg/golifehk/query"
utils "github.com/tgckpg/golifehk/utils"
)
type QueryResult struct {
+ Schedules *map[*RouteStop] *[] *Schedule
Lang string
Error error
Query *query.QueryObject
}
+func writeRouteHead( sb *strings.Builder, r *RouteStop ) {
+ utils.WriteMDv2Text( sb, r.RouteId )
+ if r.Direction == "O" {
+ sb.WriteString( "↑" )
+ } else if r.Direction == "I" {
+ sb.WriteString( "↓" )
+ }
+ if r.ServiceType != "1" {
+ utils.WriteMDv2Text( sb, utils.ToPower( r.ServiceType ) )
+ }
+}
+
+func writeShortRoute( lang *string, sb *strings.Builder, r *RouteStop ) {
+
+ if r.PrevStop() != nil {
+ utils.WriteMDv2Text( sb, (*(r.PrevStop().BusStop).Name)[ *lang ] )
+ sb.WriteString( " \\> " )
+ }
+
+ sb.WriteString( "*" )
+ utils.WriteMDv2Text( sb, (*(r.BusStop).Name)[ *lang ] )
+ sb.WriteString( "*" )
+
+ if r.NextStop() != nil {
+ sb.WriteString( " \\> " )
+ utils.WriteMDv2Text( sb, (*(r.NextStop().BusStop).Name)[ *lang ] )
+ }
+
+ sb.WriteString( "\n" )
+}
+
func ( this *QueryResult ) Message() ( string, error ) {
if this.Error != nil {
@@ -23,35 +57,119 @@ func ( this *QueryResult ) Message() ( string, error ) {
sb := strings.Builder{}
if 0 < len( *this.Query.Results ) {
- // Print Stop Name, the print the list of routes
+
+ // Print Stop Names, then print the list of routes
if this.Query.Key == "" {
+ busStops := map[string] *BusStop{}
for _, item := range *this.Query.Results {
- var b *BusStop
- b = any( item ).( *BusStop )
+ var r *RouteStop
+ r = any( item ).( *RouteStop )
+ b := r.BusStop
if b.Routes == nil {
continue
}
+ busStops[ b.BusStopId ] = b
+ }
+
+ for _, b := range busStops {
utils.WriteMDv2Text( &sb, (*b.Name)[ this.Lang ] )
sb.WriteString( "\n " )
for _, route := range *b.Routes {
- utils.WriteMDv2Text( &sb, route.RouteId )
- if route.Direction == "O" {
- sb.WriteString( "↑" )
- } else if route.Direction == "I" {
- sb.WriteString( "↓" )
- }
- if route.ServiceType != "1" {
- utils.WriteMDv2Text( &sb, utils.ToPower( route.ServiceType ) )
- }
+ writeRouteHead( &sb, route )
sb.WriteString( " " )
}
sb.WriteString( "\n" )
}
+
+ // We got a route key
+ } else {
+
+ // We also got other search keys with 1 < Results
+ // Get the ETA for this stop
+ if 1 < len( *this.Query.SearchTerms ) {
+
+ now := time.Now()
+ for _, item := range *this.Query.Results {
+ var r *RouteStop
+ r = any( item ).( *RouteStop )
+ writeRouteHead( &sb, r )
+ sb.WriteString( "\n" )
+ writeShortRoute( &this.Lang, &sb, r )
+
+ for _, schedule := range *(*this.Schedules)[ r ] {
+
+ if !schedule.ETA.IsZero() {
+
+ _m := schedule.ETA.Sub( now ).Minutes()
+
+ sb.WriteString( " \\* " )
+ txt := "%.0f min(s)"
+
+ if this.Lang == "zh-Hant" {
+ txt = "%.0f 分鐘"
+ }
+
+ utils.WriteMDv2Text( &sb, fmt.Sprintf( txt, _m ) )
+
+ if _m < 0 {
+ sb.WriteString( " 走左了?" )
+ }
+ }
+
+ if schedule.Remarks_en != "" {
+ sb.WriteString( " \\*\\* " )
+ switch this.Lang {
+ case "en":
+ utils.WriteMDv2Text( &sb, schedule.Remarks_en )
+ case "zh-Hant":
+ utils.WriteMDv2Text( &sb, schedule.Remarks_tc )
+ }
+ }
+
+ sb.WriteString( "\n" )
+ }
+
+ sb.WriteString( "\n" )
+ }
+
+ // We got only the route key, proceed to list the route stops
+ } else {
+ // Result contains all route stops, we only need the starting one
+ routes := [] *RouteStop{}
+
+ for _, item := range *this.Query.Results {
+ var r *RouteStop
+ r = any( item ).( *RouteStop )
+ if r.PrevStop() == nil {
+ routes = append( routes, r )
+ }
+ }
+
+ sort.Sort( ByRoute( routes ) )
+
+ for _, r := range routes {
+ writeRouteHead( &sb, r )
+ sb.WriteString( "\n" )
+ for {
+ b := *r.BusStop
+ utils.WriteMDv2Text( &sb, (*b.Name)[ this.Lang ] )
+ r = r.NextStop()
+ if r == nil {
+ break
+ }
+
+ sb.WriteString( " \\> " )
+ }
+ sb.WriteString( "\n" )
+ }
+
+
+ }
}
- fmt.Print( this.Query.Key )
} else {
+ return "", fmt.Errorf( "No Results" )
}
return sb.String(), nil
diff --git a/datasources/kmb/RouteStop.go b/datasources/kmb/RouteStop.go
index cde46a3..3a1a1f6 100644
--- a/datasources/kmb/RouteStop.go
+++ b/datasources/kmb/RouteStop.go
@@ -37,8 +37,29 @@ func ( routeStop RouteStop ) NextStop() *RouteStop {
return nil
}
-type ByRouteId []RouteStop
+func ( this *RouteStop ) Reload() {
-func (a ByRouteId) Len() int { return len(a) }
-func (a ByRouteId) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
-func (a ByRouteId) Less(i, j int) bool { return a[i].RouteId < a[j].RouteId }
+ searchData := [] *string{}
+ busStop := *this.BusStop
+ searchData = append( searchData, &busStop.Name_en )
+ searchData = append( searchData, &busStop.Name_tc )
+
+ this.Key = &this.RouteId
+ this.SearchData = &searchData
+}
+
+type ByRoute [] *RouteStop
+
+func (a ByRoute) Len() int { return len(a) }
+func (a ByRoute) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a ByRoute) Less(i, j int) bool {
+ _a := *a[i]
+ _b := *a[j]
+ if _a.RouteId == _b.RouteId {
+ if _a.Direction == _b.Direction {
+ return _a.ServiceType < _b.ServiceType
+ }
+ return _a.Direction < _b.Direction
+ }
+ return _a.RouteId < _b.RouteId
+}
diff --git a/datasources/kmb/query.go b/datasources/kmb/query.go
index e11ac16..5335339 100644
--- a/datasources/kmb/query.go
+++ b/datasources/kmb/query.go
@@ -11,13 +11,13 @@ func Query( lang string, message string ) query.IQueryResult {
var err error
qr := QueryResult{ Lang: lang }
- busStops, err := getBusStops()
+ routeStops, err := getRouteStops()
if err != nil {
qr.Error = err
goto qrReturn
}
- qo, err = query.Parse( strings.ToUpper( message ), busStops )
+ qo, err = query.Parse( strings.ToUpper( message ), routeStops )
if err != nil {
qr.Error = err
goto qrReturn
@@ -25,6 +25,23 @@ func Query( lang string, message string ) query.IQueryResult {
qr.Query = qo
+ if 0 < len( *qo.Results ) && 1 < len( *qo.SearchTerms ) {
+
+ rSchedules := map[*RouteStop] *[] *Schedule{}
+ for _, item := range *qo.Results {
+ var r *RouteStop
+ r = any( item ).( *RouteStop )
+ schedules, err := getSchedule( r )
+ if err != nil {
+ qr.Error = err
+ break
+ }
+
+ rSchedules[r] = schedules
+ }
+ qr.Schedules = &rSchedules
+ }
+
qrReturn:
var iqr query.IQueryResult
iqr = &qr
diff --git a/datasources/kmb/query_test.go b/datasources/kmb/query_test.go
index e99628b..b832231 100644
--- a/datasources/kmb/query_test.go
+++ b/datasources/kmb/query_test.go
@@ -18,4 +18,19 @@ func TestQuerySchedule( t *testing.T ) {
if err == nil {
t.Errorf( "Expected Error: %s, got \"\" instead", mesg )
}
+
+ qo = Query( "zh-Hant", "大欖" )
+ mesg, err = qo.Message()
+ if err != nil {
+ t.Errorf( "Unexpected Error: %s", err )
+ }
+
+ fmt.Println( mesg )
+
+ qo = Query( "zh-Hant", "261B 大欖" )
+ mesg, err = qo.Message()
+ if err != nil {
+ t.Errorf( "Unexpected Error: %s", err )
+ }
+ fmt.Println( mesg )
}
diff --git a/datasources/kmb/routestops.go b/datasources/kmb/routestops.go
index 4b85e4e..26b97f4 100644
--- a/datasources/kmb/routestops.go
+++ b/datasources/kmb/routestops.go
@@ -16,16 +16,18 @@ import (
var JSON_ROUTESTOPS string = filepath.Join( utils.WORKDIR, "kmb-routestops.json" )
var JSON_BUSSTOPS string = filepath.Join( utils.WORKDIR, "kmb-busstops.json" )
-func readRouteStopsData( busStops *map[string] *BusStop, buff *bytes.Buffer ) error {
+func readRouteStopsData( busStops *map[string] *BusStop, buff *bytes.Buffer ) ( *[] *RouteStop, error ) {
routeStopsData := RouteStops{}
err := json.Unmarshal( buff.Bytes(), &routeStopsData )
if err != nil {
- return err
+ return nil, err
}
// routeStops[ Route ][ ServiceType ][ Direction ][ Seq ] = RouteStop
routeStops := map[string] *map[string] *map[ string ] *map[ int ] *RouteStop{}
+ allRouteStops := [] *RouteStop{}
+
for _, entry := range routeStopsData.RouteStops {
busStop := (*busStops)[ entry.StationId ]
@@ -63,16 +65,17 @@ func readRouteStopsData( busStops *map[string] *BusStop, buff *bytes.Buffer ) er
if direction == nil {
direction = &map[ int ] *RouteStop{}
(*service)[ entry.Direction ] = direction
- entry.RouteStops = direction
}
+ entry.RouteStops = direction
seq := (*direction)[ entry.StationSeq ]
if seq == nil {
(*direction)[ entry.StationSeq ] = entry
}
+ allRouteStops = append( allRouteStops, entry )
}
- return nil
+ return &allRouteStops, nil
}
func readBusStopsData( buff *bytes.Buffer ) ( *map[string]*BusStop, error ) {
@@ -96,7 +99,7 @@ func readBusStopsData( buff *bytes.Buffer ) ( *map[string]*BusStop, error ) {
return &busStopMap, nil
}
-func getBusStops() (*[] query.ISearchable, error) {
+func getRouteStops() (*[] query.ISearchable, error) {
QUERY_FUNC := func() ( io.ReadCloser, error ) {
resp, err := http.Get( "https://data.etabus.gov.hk/v1/transport/kmb/stop" )
@@ -129,14 +132,15 @@ func getBusStops() (*[] query.ISearchable, error) {
return nil, err
}
- err = readRouteStopsData( busStopMap, buff )
+ routeStops, err := readRouteStopsData( busStopMap, buff )
if err != nil {
return nil, err
}
searchables := [] query.ISearchable{}
- for _, busStop := range *busStopMap {
- searchables = append( searchables, busStop )
+ for _, routeStop := range *routeStops {
+ searchables = append( searchables, routeStop )
+ routeStop.Reload()
}
return &searchables, nil
diff --git a/datasources/kmb/routestops_test.go b/datasources/kmb/routestops_test.go
index 399ca2b..cd2ccf8 100644
--- a/datasources/kmb/routestops_test.go
+++ b/datasources/kmb/routestops_test.go
@@ -5,7 +5,7 @@ import (
)
func TestRouteStops(t *testing.T) {
- _, err := getBusStops()
+ _, err := getRouteStops()
if err != nil {
t.Error( err )
}
diff --git a/datasources/kmb/schedules.go b/datasources/kmb/schedules.go
new file mode 100644
index 0000000..97726c6
--- /dev/null
+++ b/datasources/kmb/schedules.go
@@ -0,0 +1,62 @@
+package kmb
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "path/filepath"
+ "time"
+
+ "github.com/tgckpg/golifehk/utils"
+)
+
+type Schedule struct {
+ ETA time.Time `json:"eta,string"`
+ Index int `json:"eta_seq"`
+ DateCreated time.Time `json:"data_timestamp,string"`
+ Remarks_en string `json:"rmk_en"`
+ Remarks_sc string `json:"rmk_sc"`
+ Remarks_tc string `json:"rmk_tc"`
+}
+
+type Schedules struct {
+ Type string `json:"type"`
+ Version string `json:"version"`
+ DateCreated time.Time `json:"generated_timestamp,string"`
+ Schedules [] *Schedule `json:"data"`
+}
+
+func getSchedule( r *RouteStop ) ( *[] *Schedule, error ) {
+
+ CACHE_PATH := filepath.Join(
+ utils.WORKDIR,
+ "kmb-" + r.BusStop.BusStopId + "-" + r.RouteId + "-" + r.ServiceType + ".json",
+ )
+
+ QUERY_FUNC := func() ( io.ReadCloser, error ) {
+ resp, err := http.Get( fmt.Sprintf(
+ "https://data.etabus.gov.hk/v1/transport/kmb/eta/%s/%s/%s",
+ r.BusStop.BusStopId,
+ r.RouteId,
+ r.ServiceType,
+ ) )
+ if err != nil {
+ return nil, err
+ }
+ return resp.Body, nil
+ }
+
+ buff, err := utils.CacheStream( CACHE_PATH, QUERY_FUNC, 60 )
+ if err != nil {
+ return nil, err
+ }
+
+ schedules := Schedules{}
+ err = json.Unmarshal( buff.Bytes(), &schedules )
+ if err != nil {
+ return nil, err
+ }
+
+ return &schedules.Schedules, nil
+}
diff --git a/datasources/mtr/bus/QueryResult.go b/datasources/mtr/bus/QueryResult.go
index dc7db64..321c1d4 100644
--- a/datasources/mtr/bus/QueryResult.go
+++ b/datasources/mtr/bus/QueryResult.go
@@ -18,17 +18,17 @@ type QueryResult struct {
func writeShortRoute( lang *string, sb *strings.Builder, b *BusStop ) {
if b.PrevStop() != nil {
- utils.WriteMDv2Text( sb, (*b.PrevStop().Name)[ *lang ] )
+ utils.WriteMDv2Text( sb, (*b.PrevStop().Name)[ *lang ] )
sb.WriteString( " \\> " )
}
sb.WriteString( "*" )
- utils.WriteMDv2Text( sb, (*b.Name)[ *lang ] )
+ utils.WriteMDv2Text( sb, (*b.Name)[ *lang ] )
sb.WriteString( "*" )
if b.NextStop() != nil {
sb.WriteString( " \\> " )
- utils.WriteMDv2Text( sb, (*b.NextStop().Name)[ *lang ] )
+ utils.WriteMDv2Text( sb, (*b.NextStop().Name)[ *lang ] )
}
sb.WriteString( "\n" )
@@ -49,7 +49,7 @@ func ( this QueryResult ) Message() ( string, error ) {
sort.Sort( query.ByKey( *q.Results ) )
for _, entry := range *q.Results {
busStop := any( entry ).( *BusStop )
- utils.WriteMDv2Text( &sb, busStop.RouteId )
+ utils.WriteMDv2Text( &sb, busStop.RouteId )
sb.WriteString( " " )
writeShortRoute( &this.Lang, &sb, busStop )
}
@@ -79,12 +79,18 @@ func ( this QueryResult ) Message() ( string, error ) {
for _, d := range keys {
b := st[ d ]
sb.WriteString( q.Key )
- sb.WriteString( "\\-" )
- utils.WriteMDv2Text( &sb, d )
+
+ if d == "O" {
+ sb.WriteString( "↑" )
+ } else if d == "I" {
+ sb.WriteString( "↓" )
+ } else {
+ sb.WriteString( "\\?" )
+ }
sb.WriteString( "\n " )
for {
- utils.WriteMDv2Text( &sb, (*b.Name)[ this.Lang ] )
+ utils.WriteMDv2Text( &sb, (*b.Name)[ this.Lang ] )
b = b.NextStop()
if b == nil {
break
@@ -111,16 +117,16 @@ func ( this QueryResult ) Message() ( string, error ) {
for _, bus := range buses.Buses {
sb.WriteString( " \\* " )
if bus.ETAText == "" {
- utils.WriteMDv2Text( &sb, bus.ETDText )
+ utils.WriteMDv2Text( &sb, bus.ETDText )
} else {
- utils.WriteMDv2Text( &sb, bus.ETAText )
+ utils.WriteMDv2Text( &sb, bus.ETAText )
}
sb.WriteString( "\n" )
}
sb.WriteString( "\n" )
}
} else {
- utils.WriteMDv2Text( &sb, "Schedules are empty...perhaps Out of Service Time?" )
+ utils.WriteMDv2Text( &sb, "Schedules are empty...perhaps Out of Service Time?" )
}
}