commit 36e077c9100f202c4d2027cb08868fa0d1da9fec
| author | 斟酌 鵬兄 <tgckpg@gmail.com> |
| date | 2025-12-16T12:17:45Z |
| subject | Properly handle REFERENCE_ID for future changes |
commit 36e077c9100f202c4d2027cb08868fa0d1da9fec
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date: 2025-12-16T12:17:45Z
Properly handle REFERENCE_ID for future changes
---
datasources/mtr/bus/BusStop.go | 3 +
datasources/mtr/bus/QueryResult.go | 324 ++++++++++++++++++++++---------------
datasources/mtr/bus/busstops.go | 18 +--
datasources/mtr/bus/query.go | 113 +++++++------
4 files changed, 260 insertions(+), 198 deletions(-)
diff --git a/datasources/mtr/bus/BusStop.go b/datasources/mtr/bus/BusStop.go
index 8e6ea31..c11fb52 100644
--- a/datasources/mtr/bus/BusStop.go
+++ b/datasources/mtr/bus/BusStop.go
@@ -18,6 +18,9 @@ type BusStop struct {
// RouteStops[ StationSeq ] = BusStop
RouteStops *map[int]*BusStop
+ // AltRoutes
+ AltRoutes *map[string]*BusStop
+
i18n.Generics
}
diff --git a/datasources/mtr/bus/QueryResult.go b/datasources/mtr/bus/QueryResult.go
index ebe9859..9371715 100644
--- a/datasources/mtr/bus/QueryResult.go
+++ b/datasources/mtr/bus/QueryResult.go
@@ -1,148 +1,206 @@
package bus
import (
- "fmt"
- "sort"
- "strings"
+ "fmt"
+ "sort"
+ "strconv"
+ "strings"
- query "github.com/tgckpg/golifehk/query"
- utils "github.com/tgckpg/golifehk/utils"
+ query "github.com/tgckpg/golifehk/query"
+ utils "github.com/tgckpg/golifehk/utils"
)
type QueryResult struct {
- Schedules *map[*BusStop] *BusStopBuses
- Lang string
- Error error
- Query *query.QueryObject
+ Schedules *map[*BusStop]*BusStopBuses
+ Lang string
+ Error error
+ Query *query.QueryObject
}
-func writeShortRoute( lang *string, sb *strings.Builder, b *BusStop ) {
- if b.PrevStop() != nil {
- utils.WriteMDv2Text( sb, (*b.PrevStop().Name)[ *lang ] )
- sb.WriteString( " \\> " )
- }
+func writeShortRoute(lang *string, sb *strings.Builder, b *BusStop) {
+ if b.PrevStop() != nil {
+ utils.WriteMDv2Text(sb, (*b.PrevStop().Name)[*lang])
+ sb.WriteString(" \\> ")
+ }
- sb.WriteString( "*" )
- utils.WriteMDv2Text( sb, (*b.Name)[ *lang ] )
- sb.WriteString( "*" )
+ sb.WriteString("*")
+ utils.WriteMDv2Text(sb, (*b.Name)[*lang])
+ sb.WriteString("*")
- if b.NextStop() != nil {
- sb.WriteString( " \\> " )
- utils.WriteMDv2Text( sb, (*b.NextStop().Name)[ *lang ] )
- }
+ if b.NextStop() != nil {
+ sb.WriteString(" \\> ")
+ utils.WriteMDv2Text(sb, (*b.NextStop().Name)[*lang])
+ }
- sb.WriteString( "\n" )
+ sb.WriteString("\n")
}
-func ( this QueryResult ) Message() ( string, error ) {
-
- if this.Error != nil {
- return "", this.Error
- }
-
- sb := strings.Builder{}
-
- if this.Schedules == nil {
-
- q := *this.Query
-
- if len( *q.Results ) == 0 {
- terms := make( []string, len(*q.SearchTerms), len(*q.SearchTerms) )
- for i, term := range *q.SearchTerms {
- terms[i] = term.Org
- }
- return "", fmt.Errorf( "Not Found: \"%s\"", strings.Join( terms, "\", \"" ) )
- }
-
- if q.Key == "" {
- sort.Sort( query.ByKey( *q.Results ) )
- for _, entry := range *q.Results {
- busStop := any( entry ).( *BusStop )
- utils.WriteMDv2Text( &sb, busStop.RouteId )
- sb.WriteString( " " )
- writeShortRoute( &this.Lang, &sb, busStop )
- }
- } else if 1 == len( *q.SearchTerms ) {
-
- // Route listing
- st := map[string] *BusStop{}
- keys := [] string{}
-
- for _, entry := range *q.Results {
-
- busStop := any( entry ).( *BusStop )
- if _, ok := st[ busStop.Direction ]; ok {
- continue
- }
-
- st[ busStop.Direction ] = busStop
- keys = append( keys, busStop.Direction )
-
- for st[ busStop.Direction ].PrevStop() != nil {
- st[ busStop.Direction ] = st[ busStop.Direction ].PrevStop()
- }
- }
-
- sort.Strings( keys )
-
- for _, d := range keys {
- b := st[ d ]
- utils.WriteMDv2Text( &sb, q.Key )
-
- 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 ] )
- b = b.NextStop()
- if b == nil {
- break
- }
-
- sb.WriteString( " \\> " )
- }
- sb.WriteString( "\n" )
- }
-
- } else {
- return "", fmt.Errorf( "%s", "Unreachable condition occured!?" )
- }
- } else {
- if 0 < len( *this.Schedules ) {
-
- busStops := [] *BusStop{}
-
- for b, _ := range *this.Schedules {
- busStops = append( busStops, b )
- }
-
- sort.Sort( ByRoute( busStops ) )
-
- for _, busStop := range busStops {
- buses := (*this.Schedules)[ busStop ]
-
- writeShortRoute( &this.Lang, &sb, busStop )
- for _, bus := range buses.Buses {
- sb.WriteString( " \\* " )
- if bus.ETAText == "" {
- utils.WriteMDv2Text( &sb, bus.ETDText )
- } else {
- utils.WriteMDv2Text( &sb, bus.ETAText )
- }
- sb.WriteString( "\n" )
- }
- sb.WriteString( "\n" )
- }
- } else {
- utils.WriteMDv2Text( &sb, "Schedules are empty...perhaps Out of Service Time?" )
- }
- }
-
- return sb.String(), nil
+func (this QueryResult) Message() (string, error) {
+
+ if this.Error != nil {
+ return "", this.Error
+ }
+
+ sb := strings.Builder{}
+
+ if this.Schedules == nil {
+
+ q := *this.Query
+
+ if len(*q.Results) == 0 {
+ terms := make([]string, len(*q.SearchTerms), len(*q.SearchTerms))
+ for i, term := range *q.SearchTerms {
+ terms[i] = term.Org
+ }
+ return "", fmt.Errorf("Not Found: \"%s\"", strings.Join(terms, "\", \""))
+ }
+
+ if q.Key == "" {
+ sort.Sort(query.ByKey(*q.Results))
+ for _, entry := range *q.Results {
+ busStop := any(entry).(*BusStop)
+ utils.WriteMDv2Text(&sb, busStop.RouteId)
+ sb.WriteString(" ")
+ writeShortRoute(&this.Lang, &sb, busStop)
+ }
+ } else if 1 == len(*q.SearchTerms) {
+
+ // Route listing
+ busLines := map[string]map[string]*BusStop{}
+
+ for _, entry := range *q.Results {
+
+ busStop := any(entry).(*BusStop)
+ busRoutes := busLines[busStop.ReferenceId]
+
+ if busRoutes == nil {
+ busRoutes = make(map[string]*BusStop)
+ busLines[busStop.ReferenceId] = busRoutes
+ }
+
+ if _, ok := busRoutes[busStop.Direction]; ok {
+ continue
+ }
+
+ busRoutes[busStop.Direction] = busStop
+
+ for busRoutes[busStop.Direction].PrevStop() != nil {
+ busRoutes[busStop.Direction] = busRoutes[busStop.Direction].PrevStop()
+ }
+ }
+
+ // Sort Bus Lines
+ lineKeys := make([]string, 0, len(busLines))
+ for k := range busLines {
+ lineKeys = append(lineKeys, k)
+ }
+
+ sort.Slice(lineKeys, func(i, j int) bool {
+ ai, an := splitKey(lineKeys[i])
+ bi, bn := splitKey(lineKeys[j])
+
+ // different base → normal string compare
+ if ai != bi {
+ return ai < bi
+ }
+
+ // same base: base key comes first
+ if an == -1 && bn != -1 {
+ return true
+ }
+ if an != -1 && bn == -1 {
+ return false
+ }
+
+ // both have suffix → numeric compare
+ return an < bn
+ })
+
+ for _, lineKey := range lineKeys {
+ busRoutes := busLines[lineKey]
+
+ // Sort route directions
+ dirKeys := make([]string, 0, len(busRoutes))
+ for k := range busRoutes {
+ dirKeys = append(dirKeys, k)
+ }
+
+ sort.Strings(dirKeys)
+
+ for _, d := range dirKeys {
+ b := busRoutes[d]
+
+ utils.WriteMDv2Text(&sb, lineKey)
+
+ 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])
+ b = b.NextStop()
+ if b == nil {
+ break
+ }
+
+ sb.WriteString(" \\> ")
+ }
+ sb.WriteString("\n")
+ }
+ }
+
+ } else {
+ return "", fmt.Errorf("%s", "Unreachable condition occured!?")
+ }
+ } else {
+ if 0 < len(*this.Schedules) {
+
+ busStops := []*BusStop{}
+
+ for b, _ := range *this.Schedules {
+ busStops = append(busStops, b)
+ }
+
+ sort.Sort(ByRoute(busStops))
+
+ for _, busStop := range busStops {
+ buses := (*this.Schedules)[busStop]
+
+ writeShortRoute(&this.Lang, &sb, busStop)
+ for _, bus := range buses.Buses {
+ sb.WriteString(" \\* ")
+ if bus.ETAText == "" {
+ utils.WriteMDv2Text(&sb, bus.ETDText)
+ } else {
+ utils.WriteMDv2Text(&sb, bus.ETAText)
+ }
+ sb.WriteString("\n")
+ }
+ sb.WriteString("\n")
+ }
+ } else {
+ utils.WriteMDv2Text(&sb, "Schedules are empty...perhaps Out of Service Time?")
+ }
+ }
+
+ return sb.String(), nil
+}
+
+func splitKey(s string) (base string, num int) {
+ parts := strings.SplitN(s, "-", 2)
+ if len(parts) == 1 {
+ return s, -1 // base key, no suffix
+ }
+
+ n, err := strconv.Atoi(parts[1])
+ if err != nil {
+ return s, -1 // fallback: treat as base
+ }
+
+ return parts[0], n
}
diff --git a/datasources/mtr/bus/busstops.go b/datasources/mtr/bus/busstops.go
index aa0410b..da47e13 100644
--- a/datasources/mtr/bus/busstops.go
+++ b/datasources/mtr/bus/busstops.go
@@ -4,7 +4,6 @@ import (
"encoding/csv"
"fmt"
"io"
- "log"
"net/http"
"path/filepath"
"strconv"
@@ -63,21 +62,16 @@ func readBusStopData(r io.Reader) (*map[string]*BusStop, error) {
}
}
- // Ignoring special route for now as getSchedule does not reflect
- // which bus is a special route
- if entry.ReferenceId != entry.RouteId {
- log.Printf("Ignoring special Route: %s", entry.ReferenceId)
- continue
- }
+ entryId := fmt.Sprintf("%s:%s", entry.ReferenceId, entry.StationId)
- if busStops[entry.StationId] != nil {
+ if busStops[entryId] != nil {
return nil, fmt.Errorf("Duplicated entry %+v", entry)
}
- routeDir, hasKey := routeStops[entry.RouteId]
+ routeDir, hasKey := routeStops[entry.ReferenceId]
if !hasKey {
- routeStops[entry.RouteId] = map[string]map[int]*BusStop{}
- routeDir = routeStops[entry.RouteId]
+ routeStops[entry.ReferenceId] = map[string]map[int]*BusStop{}
+ routeDir = routeStops[entry.ReferenceId]
}
route, hasKey := routeDir[entry.Direction]
@@ -94,7 +88,7 @@ func readBusStopData(r io.Reader) (*map[string]*BusStop, error) {
entry.RouteStops = &route
entry.Reload()
- busStops[entry.StationId] = &entry
+ busStops[entryId] = &entry
}
return &busStops, nil
}
diff --git a/datasources/mtr/bus/query.go b/datasources/mtr/bus/query.go
index a337812..3e9ed04 100644
--- a/datasources/mtr/bus/query.go
+++ b/datasources/mtr/bus/query.go
@@ -1,59 +1,66 @@
package bus
import (
- "strings"
- query "github.com/tgckpg/golifehk/query"
+ "log"
+ "strings"
+
+ query "github.com/tgckpg/golifehk/query"
)
-func Query( lang string, message string ) query.IQueryResult {
-
- var qBusStops *query.QueryObject
- var err error
-
- qr := QueryResult{ Lang: lang }
- busStops, err := getBusStops()
- if err != nil {
- qr.Error = err
- goto qrReturn
- }
-
- qBusStops, err = query.Parse( strings.ToUpper( message ), busStops )
- if err != nil {
- qr.Error = err
- goto qrReturn
- }
-
- qr.Query = qBusStops
- if 0 < len( *qBusStops.Results ) && 1 < len( *qBusStops.SearchTerms ) {
- schedules, err := getSchedule( lang, qBusStops.Key )
- if err != nil {
- qr.Error = err
- goto qrReturn
- }
-
- if len( schedules.BusStops ) == 0 {
- qr.Schedules = &map[*BusStop] *BusStopBuses{}
- goto qrReturn
- }
-
- matches := map[*BusStop] *BusStopBuses{}
- for _, entry := range *qBusStops.Results {
- busStop := any( entry ).( *BusStop )
-
- for _, busStopSch := range schedules.BusStops {
- if busStopSch.BusStopId == busStop.StationId {
- matches[busStop] = &busStopSch
- break
- }
- }
-
- }
-
- qr.Schedules = &matches
- }
-
- qrReturn:
- var iqr query.IQueryResult
- iqr = &qr
- return iqr
+func Query(lang string, message string) query.IQueryResult {
+
+ var qBusStops *query.QueryObject
+ var err error
+
+ qr := QueryResult{Lang: lang}
+ busStops, err := getBusStops()
+ if err != nil {
+ qr.Error = err
+ goto qrReturn
+ }
+
+ qBusStops, err = query.Parse(strings.ToUpper(message), busStops)
+ if err != nil {
+ qr.Error = err
+ goto qrReturn
+ }
+
+ qr.Query = qBusStops
+ if 0 < len(*qBusStops.Results) && 1 < len(*qBusStops.SearchTerms) {
+ schedules, err := getSchedule(lang, qBusStops.Key)
+ if err != nil {
+ qr.Error = err
+ goto qrReturn
+ }
+
+ if len(schedules.BusStops) == 0 {
+ qr.Schedules = &map[*BusStop]*BusStopBuses{}
+ goto qrReturn
+ }
+
+ matches := map[*BusStop]*BusStopBuses{}
+ for _, entry := range *qBusStops.Results {
+ busStop := any(entry).(*BusStop)
+
+ for _, busStopSch := range schedules.BusStops {
+ if busStopSch.BusStopId == busStop.StationId {
+ if busStop.RouteId != busStop.ReferenceId {
+ // There were no indicator for special routes from getSchedule API
+ log.Printf("Ignoring special route matches: %s", busStop.ReferenceId)
+ continue
+ }
+ matches[busStop] = &busStopSch
+ break
+ }
+ }
+
+ }
+
+ qr.Schedules = &matches
+ }
+
+qrReturn:
+ var iqr query.IQueryResult
+ iqr = &qr
+ return iqr
}