Added kmb schedules

This commit is contained in:
斟酌 鵬兄 2022-09-25 18:48:22 +08:00
parent b251e35be4
commit 41be1db381
8 changed files with 283 additions and 40 deletions

View File

@ -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
if this.Query.Key == "" {
for _, item := range *this.Query.Results {
var b *BusStop
b = any( item ).( *BusStop )
// Print Stop Names, then print the list of routes
if this.Query.Key == "" {
busStops := map[string] *BusStop{}
for _, item := range *this.Query.Results {
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

View File

@ -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
}

View File

@ -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

View File

@ -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 )
}

View File

@ -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

View File

@ -5,7 +5,7 @@ import (
)
func TestRouteStops(t *testing.T) {
_, err := getBusStops()
_, err := getRouteStops()
if err != nil {
t.Error( err )
}

View File

@ -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
}

View File

@ -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?" )
}
}