Initial commit
This commit is contained in:
		
							
								
								
									
										78
									
								
								businfo/mtr/busschedule.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								businfo/mtr/busschedule.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
package mtr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
    "fmt"
 | 
			
		||||
    // "byte"
 | 
			
		||||
    // "net/http"
 | 
			
		||||
    "io"
 | 
			
		||||
    "os"
 | 
			
		||||
    "encoding/json"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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 ) {
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    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
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
    data, err := io.ReadAll( resp.Body )
 | 
			
		||||
    /*/
 | 
			
		||||
    f, err := os.Open( "abc.json" )
 | 
			
		||||
    defer f.Close()
 | 
			
		||||
 | 
			
		||||
    data, err := io.ReadAll( f )
 | 
			
		||||
    //*/
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        return nil, err
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    schedules := BusSchedule{}
 | 
			
		||||
 | 
			
		||||
    err = json.Unmarshal( data, &schedules )
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        return nil, err
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fmt.Printf( "%+v", schedules )
 | 
			
		||||
    return &schedules, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								businfo/mtr/busschedule_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								businfo/mtr/busschedule_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
package mtr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGetSchedule(t *testing.T) {
 | 
			
		||||
    _, err := getSchedule( "en", "K66" )
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        t.Error( err )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										141
									
								
								businfo/mtr/busstops.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								businfo/mtr/busstops.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,141 @@
 | 
			
		||||
package mtr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
    "encoding/csv"
 | 
			
		||||
    "fmt"
 | 
			
		||||
    "io"
 | 
			
		||||
    "net/http"
 | 
			
		||||
    "os"
 | 
			
		||||
    "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 DEF_CSV 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) {
 | 
			
		||||
 | 
			
		||||
    if mBusStops != nil {
 | 
			
		||||
        return mBusStops, nil
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mBusStops, err := pullLocal()
 | 
			
		||||
    if mBusStops != nil {
 | 
			
		||||
        return mBusStops, err
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mBusStops, err = pullRemote()
 | 
			
		||||
    if mBusStops != nil {
 | 
			
		||||
        return mBusStops, err
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return nil, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func pullRemote() (*map[string]BusStop, error) {
 | 
			
		||||
 | 
			
		||||
    err := os.MkdirAll( filepath.Dir( DEF_CSV ), 0750 )
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        return nil, err
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    resp, err := http.Get( "https://opendata.mtr.com.hk/data/mtr_bus_stops.csv" )
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        return nil, err
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
    f, err := os.OpenFile( DEF_CSV, os.O_CREATE | os.O_RDWR, 0644 )
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        return nil, err
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    defer f.Close()
 | 
			
		||||
 | 
			
		||||
    _, err = io.Copy( f, resp.Body )
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        return nil, err
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    f.Seek( 0, 0 )
 | 
			
		||||
    return readBusStopData( f )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func pullLocal() (*map[string]BusStop, error) {
 | 
			
		||||
    f, err := os.Open( DEF_CSV )
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        return nil, err
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    defer f.Close()
 | 
			
		||||
 | 
			
		||||
    return readBusStopData( f )
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								businfo/mtr/busstops_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								businfo/mtr/busstops_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
package mtr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
    "fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestAll(t *testing.T) {
 | 
			
		||||
    /*
 | 
			
		||||
    entries, err := pullRemote()
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        t.Error( err )
 | 
			
		||||
    }
 | 
			
		||||
    fmt.Println( entries )
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    entries, err := pullLocal()
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        t.Error( err )
 | 
			
		||||
    }
 | 
			
		||||
    fmt.Println( entries )
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										0
									
								
								businfo/mtr/k66.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								businfo/mtr/k66.json
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										91
									
								
								businfo/mtr/query.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								businfo/mtr/query.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
			
		||||
package mtr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
    "fmt"
 | 
			
		||||
    "strings"
 | 
			
		||||
    "github.com/tgckpg/golifehk/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type QueryObject struct {
 | 
			
		||||
    Route string
 | 
			
		||||
    BusStops []BusStop
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Term struct {
 | 
			
		||||
    Value string
 | 
			
		||||
    ProblyRoute bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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{}
 | 
			
		||||
 | 
			
		||||
    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 )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 != "" && 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
									
								
								businfo/mtr/query_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								businfo/mtr/query_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
package mtr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
    "fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestQuerySchedule(t *testing.T) {
 | 
			
		||||
    qo, err := parse( "K74 天瑞" )
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        t.Error( err )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fmt.Println( qo )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user