Generalize cache get with expire param
This commit is contained in:
76
datasources/mtr/bus/busschedule.go
Normal file
76
datasources/mtr/bus/busschedule.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package bus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/tgckpg/golifehk/utils"
|
||||
)
|
||||
|
||||
type Location struct {
|
||||
latitude float32
|
||||
longitude float32
|
||||
}
|
||||
|
||||
type Bus struct {
|
||||
ETA int `json:"arrivalTimeInSecond,string"`
|
||||
ETAText string `json:"arrivalTimeText"`
|
||||
BusId string `json:"busId"`
|
||||
BusLocation Location `json:"busLocation"`
|
||||
ETD string `json:"departureTimeInSecond"`
|
||||
ETDText string `json:"departureTimeText"`
|
||||
Delayed string `json:"isDelayed"`
|
||||
Scheduled string `json:"isScheduled"`
|
||||
LineRef string `json:"lineRef"`
|
||||
}
|
||||
|
||||
type BusStopSchedules struct {
|
||||
Buses [] Bus `json:"bus"`
|
||||
Suspended string `json:"isSuspended"`
|
||||
}
|
||||
|
||||
type BusSchedule struct {
|
||||
RefreshTime int `json:"appRefreshTimeInSecond,string"`
|
||||
BusStops [] BusStopSchedules `json:"busStop"`
|
||||
}
|
||||
|
||||
func getSchedule( lang string, routeName string ) ( *BusSchedule, error ) {
|
||||
|
||||
CACHE_PATH := filepath.Join(
|
||||
utils.WORKDIR,
|
||||
"mtr_bsch" + "-" + lang + "-" + routeName + ".json",
|
||||
)
|
||||
|
||||
QUERY_FUNC := func() ( io.ReadCloser, error ) {
|
||||
// Query Remote
|
||||
values := map[string]string { "language": lang , "routeName": routeName }
|
||||
jsonValue, _ := json.Marshal(values)
|
||||
resp, err := http.Post(
|
||||
"https://rt.data.gov.hk/v1/transport/mtr/bus/getSchedule",
|
||||
"application/json",
|
||||
bytes.NewBuffer( jsonValue ),
|
||||
)
|
||||
|
||||
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 := BusSchedule{}
|
||||
err = json.Unmarshal( buff.Bytes(), &schedules )
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &schedules, nil
|
||||
}
|
12
datasources/mtr/bus/busschedule_test.go
Normal file
12
datasources/mtr/bus/busschedule_test.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package bus
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetSchedule(t *testing.T) {
|
||||
_, err := getSchedule( "en", "K66" )
|
||||
if err != nil {
|
||||
t.Error( err )
|
||||
}
|
||||
}
|
98
datasources/mtr/bus/busstops.go
Normal file
98
datasources/mtr/bus/busstops.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package bus
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tgckpg/golifehk/utils"
|
||||
)
|
||||
|
||||
type BusStop struct {
|
||||
RouteId string
|
||||
Direction string
|
||||
StationSeq string
|
||||
StationId string
|
||||
Latitude float64
|
||||
Longtitude float64
|
||||
Name_zhant string
|
||||
Name_en string
|
||||
}
|
||||
|
||||
var mBusStops *map[string]BusStop
|
||||
|
||||
var CSV_BUSSTOPS string = filepath.Join( utils.WORKDIR, "mtr_bus_stops.csv" )
|
||||
|
||||
func readBusStopData( r io.Reader ) ( *map[string]BusStop, error ) {
|
||||
|
||||
reader := csv.NewReader( r )
|
||||
entries, err := reader.ReadAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
busStops := map[string]BusStop{}
|
||||
var headers []string
|
||||
for i, line := range entries {
|
||||
if i == 0 {
|
||||
headers = line
|
||||
line[0] = strings.TrimLeft( line[0], utils.BOM )
|
||||
continue
|
||||
}
|
||||
|
||||
var entry BusStop
|
||||
for j, value := range line {
|
||||
switch headers[j] {
|
||||
case "ROUTE_ID":
|
||||
entry.RouteId = value
|
||||
case "DIRECTION":
|
||||
entry.Direction = value
|
||||
case "STATION_SEQNO":
|
||||
entry.StationSeq = value
|
||||
case "STATION_ID":
|
||||
entry.StationId = value
|
||||
case "STATION_LATITUDE":
|
||||
v, _ := strconv.ParseFloat( value, 64 )
|
||||
entry.Latitude = v
|
||||
case "STATION_LONGITUDE":
|
||||
v, _ := strconv.ParseFloat( value, 64 )
|
||||
entry.Longtitude = v
|
||||
case "STATION_NAME_CHI":
|
||||
entry.Name_zhant = value
|
||||
case "STATION_NAME_ENG":
|
||||
entry.Name_en = value
|
||||
default:
|
||||
return nil, fmt.Errorf( "Unknown header \"%s\"", headers[j] )
|
||||
}
|
||||
}
|
||||
|
||||
if _, t := busStops[ entry.StationId ]; t {
|
||||
return nil, fmt.Errorf( "Duplicated entry %+v", entry )
|
||||
}
|
||||
|
||||
busStops[ entry.StationId ] = entry
|
||||
}
|
||||
return &busStops, nil
|
||||
}
|
||||
|
||||
func getBusStops() (*map[string]BusStop, error) {
|
||||
|
||||
QUERY_FUNC := func() ( io.ReadCloser, error ) {
|
||||
resp, err := http.Get( "https://opendata.mtr.com.hk/data/mtr_bus_stops.csv" )
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
buff, err := utils.CacheStream( CSV_BUSSTOPS, QUERY_FUNC, 7 * 24 * 3600 )
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return readBusStopData( buff )
|
||||
}
|
8
datasources/mtr/bus/busstops_test.go
Normal file
8
datasources/mtr/bus/busstops_test.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package bus
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
}
|
103
datasources/mtr/bus/query.go
Normal file
103
datasources/mtr/bus/query.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package bus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"github.com/tgckpg/golifehk/utils"
|
||||
)
|
||||
|
||||
type QueryObject struct {
|
||||
Route string
|
||||
BusStops *[]BusStop
|
||||
}
|
||||
|
||||
type QueryResult struct {
|
||||
BusStops []BusStop
|
||||
err string
|
||||
}
|
||||
|
||||
type Term struct {
|
||||
Value string
|
||||
ProblyRoute bool
|
||||
}
|
||||
|
||||
func Query( message string ) *QueryResult {
|
||||
return &QueryResult{ err: "No Result" }
|
||||
}
|
||||
|
||||
func test( entry BusStop, val string ) bool {
|
||||
switch true {
|
||||
case strings.Contains( entry.Name_zhant, val ):
|
||||
fallthrough
|
||||
case strings.Contains( entry.Name_en, val ):
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func parse( line string ) ( *QueryObject, error ) {
|
||||
busStops, err := getBusStops()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var route string = ""
|
||||
var searches = []string{}
|
||||
matches := []BusStop{}
|
||||
|
||||
// Sanitize and assume properties for each of the keywords
|
||||
terms := []Term{}
|
||||
for _, val := range strings.Split( line, " " ) {
|
||||
val = strings.ToUpper( strings.Trim( val, " " ) )
|
||||
term := Term{
|
||||
Value: val,
|
||||
ProblyRoute: strings.ContainsAny( val, utils.ROUTE_CHARS ),
|
||||
}
|
||||
terms = append( terms, term )
|
||||
}
|
||||
|
||||
// Search for route name first, otherwise search in other props
|
||||
for _, entry := range *busStops {
|
||||
|
||||
// Search for RouteId
|
||||
for _, term := range terms {
|
||||
|
||||
if term.ProblyRoute && term.Value == entry.RouteId {
|
||||
if route != "" && route != term.Value {
|
||||
return nil, fmt.Errorf( "Cannot %s & %s", route, term.Value )
|
||||
}
|
||||
matches = append( matches, entry )
|
||||
route = entry.RouteId
|
||||
break
|
||||
}
|
||||
|
||||
searches = append( searches, term.Value )
|
||||
if test( entry, term.Value ) {
|
||||
matches = append( matches, entry )
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If route found, filter out all other route
|
||||
// then search within that route
|
||||
if route != "" && 0 < len( searches ) {
|
||||
matches_in := []BusStop{}
|
||||
for _, entry := range matches {
|
||||
if entry.RouteId != route {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, val := range searches {
|
||||
if test( entry, val ) {
|
||||
matches_in = append( matches_in, entry )
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
matches = matches_in
|
||||
}
|
||||
|
||||
return &QueryObject{ Route: route, BusStops: &matches }, err
|
||||
}
|
16
datasources/mtr/bus/query_test.go
Normal file
16
datasources/mtr/bus/query_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package bus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestQuerySchedule(t *testing.T) {
|
||||
qo, err := parse( "K74 天瑞" )
|
||||
if err != nil {
|
||||
t.Error( err )
|
||||
}
|
||||
|
||||
fmt.Printf( "%+v", qo )
|
||||
}
|
||||
|
Reference in New Issue
Block a user