Initial commit
This commit is contained in:
commit
7e327abbae
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.csv
|
||||
*.json
|
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 )
|
||||
}
|
||||
|
11
handlers/index.go
Normal file
11
handlers/index.go
Normal file
@ -0,0 +1,11 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Index(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
|
||||
}
|
12
main.go
Normal file
12
main.go
Normal file
@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"github.com/tgckpg/golifehk/handlers"
|
||||
)
|
||||
|
||||
func main() {
|
||||
http.HandleFunc( "/", handlers.Index )
|
||||
log.Fatal(http.ListenAndServe(":8000", nil))
|
||||
}
|
12
utils/env.go
Normal file
12
utils/env.go
Normal file
@ -0,0 +1,12 @@
|
||||
package utils
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var WORKDIR string = TryGetEnv( "GOLIFEHK_WORKDIR", filepath.Join( os.TempDir(), "golifehk" ) )
|
||||
|
||||
var BOM string = bytes.NewBuffer([]byte{ 0xEF, 0xBB, 0xBF }).String()
|
||||
|
||||
const ROUTE_CHARS string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"
|
45
utils/shortcuts.go
Normal file
45
utils/shortcuts.go
Normal file
@ -0,0 +1,45 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func TryGetEnv[T any]( name string, fallback T ) T {
|
||||
v := os.Getenv( name )
|
||||
|
||||
if v != "" {
|
||||
switch any( fallback ).(type) {
|
||||
case uint64:
|
||||
p, err := strconv.ParseUint( v, 10, 64 )
|
||||
if err == nil {
|
||||
return any( uint64( p ) ).(T)
|
||||
}
|
||||
case uint32:
|
||||
p, err := strconv.ParseUint( v, 10, 32 )
|
||||
if err == nil {
|
||||
return any( uint32( p ) ).(T)
|
||||
}
|
||||
case int:
|
||||
p, err := strconv.ParseInt( v, 10, 32 )
|
||||
if err == nil {
|
||||
return any( int( p ) ).(T)
|
||||
}
|
||||
case float64:
|
||||
p, err := strconv.ParseFloat( v, 64 )
|
||||
if err == nil {
|
||||
return any( float64( p ) ).(T)
|
||||
}
|
||||
case float32:
|
||||
p, err := strconv.ParseFloat( v, 32 )
|
||||
if err == nil {
|
||||
return any( float32( p ) ).(T)
|
||||
}
|
||||
default:
|
||||
return any( v ).(T)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return fallback
|
||||
}
|
29
utils/shortcuts_test.go
Normal file
29
utils/shortcuts_test.go
Normal file
@ -0,0 +1,29 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testType[T any]( t *testing.T, name string, fallback T ) T {
|
||||
got := TryGetEnv( "ABC", fallback )
|
||||
a := reflect.TypeOf( got ).Kind()
|
||||
b := reflect.TypeOf( fallback ).Kind()
|
||||
if a != b {
|
||||
t.Errorf( "%s is not of type %s", any( got ), b )
|
||||
}
|
||||
return got
|
||||
}
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
testType( t, "ABC", "Test" )
|
||||
testType( t, "ABC", 11 )
|
||||
|
||||
os.Setenv( "ABC", "22" )
|
||||
got := testType( t, "ABC", 11 )
|
||||
if got != 22 {
|
||||
t.Errorf( "Expected 22, got %d", got )
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user