diff --git a/datasources/mtr/bus/BusStop.go b/datasources/mtr/bus/BusStop.go index cd8a400..8e6ea31 100644 --- a/datasources/mtr/bus/BusStop.go +++ b/datasources/mtr/bus/BusStop.go @@ -1,62 +1,63 @@ package bus import ( - i18n "github.com/tgckpg/golifehk/i18n" + i18n "github.com/tgckpg/golifehk/i18n" ) type BusStop struct { - RouteId string - Direction string - StationSeq int - StationId string - Latitude float64 - Longtitude float64 - Name_zh string - Name_en string + RouteId string + ReferenceId string + Direction string + StationSeq int + StationId string + Latitude float64 + Longtitude float64 + Name_zh string + Name_en string - // RouteStops[ StationSeq ] = BusStop - RouteStops *map[int] *BusStop + // RouteStops[ StationSeq ] = BusStop + RouteStops *map[int]*BusStop - i18n.Generics + i18n.Generics } -func ( this *BusStop ) PrevStop() *BusStop { - if v, hasKey := (*this.RouteStops)[ this.StationSeq - 1 ]; hasKey { - return v - } - return nil +func (this *BusStop) PrevStop() *BusStop { + if v, hasKey := (*this.RouteStops)[this.StationSeq-1]; hasKey { + return v + } + return nil } -func ( this *BusStop ) NextStop() *BusStop { - if v, hasKey := (*this.RouteStops)[ this.StationSeq + 1 ]; hasKey { - return v - } - return nil +func (this *BusStop) NextStop() *BusStop { + if v, hasKey := (*this.RouteStops)[this.StationSeq+1]; hasKey { + return v + } + return nil } -func ( this *BusStop ) Reload() { - i18n_Name := map[string] string{} - i18n_Name["en"] = this.Name_en - i18n_Name["zh-Hant"] = this.Name_zh +func (this *BusStop) Reload() { + i18n_Name := map[string]string{} + i18n_Name["en"] = this.Name_en + i18n_Name["zh-Hant"] = this.Name_zh - searchData := [] *string{} - searchData = append( searchData, &this.Name_en ) - searchData = append( searchData, &this.Name_zh ) + searchData := []*string{} + searchData = append(searchData, &this.Name_en) + searchData = append(searchData, &this.Name_zh) - this.Name = &i18n_Name - this.Key = &this.RouteId - this.SearchData = &searchData + this.Name = &i18n_Name + this.Key = &this.RouteId + this.SearchData = &searchData } -type ByRoute [] *BusStop +type ByRoute []*BusStop -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) 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 { - return _a.Direction < _b.Direction - } - return _a.RouteId < _b.RouteId + _a := *a[i] + _b := *a[j] + if _a.RouteId == _b.RouteId { + return _a.Direction < _b.Direction + } + return _a.RouteId < _b.RouteId } diff --git a/datasources/mtr/bus/busschedule.go b/datasources/mtr/bus/busschedule.go index 12f3704..cdd3d58 100644 --- a/datasources/mtr/bus/busschedule.go +++ b/datasources/mtr/bus/busschedule.go @@ -1,146 +1,146 @@ package bus import ( - "fmt" - "bytes" - "encoding/json" - "io" - "log" - "net/http" - "path/filepath" - "time" + "bytes" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "path/filepath" + "time" - "github.com/tgckpg/golifehk/utils" + "github.com/tgckpg/golifehk/utils" ) type Location struct { - latitude float32 - longitude float32 + 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"` + 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 BusStopBuses struct { - Buses [] Bus `json:"bus"` - BusStopId string `json:"busStopId"` - Suspended string `json:"isSuspended"` + Buses []Bus `json:"bus"` + BusStopId string `json:"busStopId"` + Suspended string `json:"isSuspended"` } type ScheduleStatusTime struct { - time.Time + time.Time } type BusSchedule struct { - RefreshTime int `json:"appRefreshTimeInSecond,string"` - BusStops [] BusStopBuses `json:"busStop"` - StatusTime ScheduleStatusTime `json:"routeStatusTime"` - RemarksTitle string `json:"routeStatusRemarkTitle"` - // 0 = OK - // 100 = Rate limit ( Unconfirmed. Not in spec ) - Status int `json:"status,string"` + RefreshTime int `json:"appRefreshTimeInSecond,string"` + BusStops []BusStopBuses `json:"busStop"` + StatusTime ScheduleStatusTime `json:"routeStatusTime"` + RemarksTitle string `json:"routeStatusRemarkTitle"` + // 0 = OK + // 100 = Rate limit ( Unconfirmed. Not in spec ) + Status int `json:"status,string"` } func (t *ScheduleStatusTime) UnmarshalJSON(b []byte) (err error) { - date, err := time.Parse(`"2006\/01\/02 15:04"`, string(b)) - if err != nil { - return err - } - t.Time = date - return + date, err := time.Parse(`"2006\/01\/02 15:04"`, string(b)) + if err != nil { + return err + } + t.Time = date + return } -func getSchedule( lang string, routeName string ) ( *BusSchedule, error ) { +func getSchedule(lang string, routeName string) (*BusSchedule, error) { - CACHE_PATH := filepath.Join( - utils.WORKDIR, - "mtr_bsch" + "-" + lang + "-" + routeName + ".json", - ) + CACHE_PATH := filepath.Join( + utils.WORKDIR, + "mtr_bsch"+"-"+lang+"-"+routeName+".json", + ) - postLang := "en" - if lang == "zh-Hant" { - postLang = "zh" - } + postLang := "en" + if lang == "zh-Hant" { + postLang = "zh" + } - QUERY_FUNC := func() ( io.ReadCloser, error ) { - // Query Remote - values := map[string]string { "language": postLang, "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 ), - ) + QUERY_FUNC := func() (io.ReadCloser, error) { + // Query Remote + values := map[string]string{"language": postLang, "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 - } + if err != nil { + return nil, err + } - return resp.Body, nil - } + return resp.Body, nil + } - cs, err := utils.CacheStreamEx( CACHE_PATH, QUERY_FUNC ) - if err != nil { - return nil, err - } + cs, err := utils.CacheStreamEx(CACHE_PATH, QUERY_FUNC) + if err != nil { + return nil, err + } - oldSch := BusSchedule{ - Status: -1, - } + oldSch := BusSchedule{ + Status: -1, + } - if cs.Local != nil { - err = json.Unmarshal( cs.Local.Bytes(), &oldSch ) - if err != nil { - return nil, err - } - } + if cs.Local != nil { + err = json.Unmarshal(cs.Local.Bytes(), &oldSch) + if err != nil { + return nil, err + } + } - newSch := BusSchedule{ - Status: -1, - } + newSch := BusSchedule{ + Status: -1, + } - for i := 0; i < 3; i ++ { + for i := 0; i < 3; i++ { - if cs.Remote != nil { - err = json.Unmarshal( cs.Remote.Bytes(), &newSch ) - if err != nil { - return nil, err - } - } + if cs.Remote != nil { + err = json.Unmarshal(cs.Remote.Bytes(), &newSch) + if err != nil { + return nil, err + } + } - if newSch.Status == 0 { - cs.Commit() - return &newSch, nil - } + if newSch.Status == 0 { + cs.Commit() + return &newSch, nil + } - if oldSch.Status == 0 { - if cs.NotExpired( 60 ) || oldSch.StatusTime.Time == newSch.StatusTime.Time { - log.Printf( "Using cache: %s", CACHE_PATH ) - return &oldSch, nil - } - } + if oldSch.Status == 0 { + if cs.NotExpired(60) || oldSch.StatusTime.Time == newSch.StatusTime.Time { + log.Printf("Using cache: %s", CACHE_PATH) + return &oldSch, nil + } + } - // First time + try again i times - err = cs.Reload() - log.Printf( "Reloading (%d): %s", i, CACHE_PATH ) - if err != nil { - err = fmt.Errorf( "Error retrieving data: %s", err ) - return nil, err - } - } + // First time + try again i times + err = cs.Reload() + log.Printf("Reloading (%d): %s", i, CACHE_PATH) + if err != nil { + err = fmt.Errorf("Error retrieving data: %s", err) + return nil, err + } + } - if newSch.Status != 0 { - err = fmt.Errorf( "%s (%d)", newSch.RemarksTitle, newSch.Status ) - } + if newSch.Status != 0 { + err = fmt.Errorf("%s (%d)", newSch.RemarksTitle, newSch.Status) + } - return &newSch, err + return &newSch, err } diff --git a/datasources/mtr/bus/busstops.go b/datasources/mtr/bus/busstops.go index f3fa7d8..aa0410b 100644 --- a/datasources/mtr/bus/busstops.go +++ b/datasources/mtr/bus/busstops.go @@ -1,120 +1,130 @@ package bus import ( - "encoding/csv" - "fmt" - "io" - "net/http" - "path/filepath" - "strconv" + "encoding/csv" + "fmt" + "io" + "log" + "net/http" + "path/filepath" + "strconv" - query "github.com/tgckpg/golifehk/query" - "github.com/tgckpg/golifehk/utils" + query "github.com/tgckpg/golifehk/query" + "github.com/tgckpg/golifehk/utils" ) -var CSV_BUSSTOPS string = filepath.Join( utils.WORKDIR, "mtr_bus_stops.csv" ) +var CSV_BUSSTOPS string = filepath.Join(utils.WORKDIR, "mtr_bus_stops.csv") -func readBusStopData( r io.Reader ) ( *map[string] *BusStop, error ) { +func readBusStopData(r io.Reader) (*map[string]*BusStop, error) { - reader := csv.NewReader( r ) - entries, err := reader.ReadAll() - if err != nil { - return nil, err - } + reader := csv.NewReader(r) + entries, err := reader.ReadAll() + if err != nil { + return nil, err + } - busStops := map[string] *BusStop{} - routeStops := map[string] map[string] map[int] *BusStop{} + busStops := map[string]*BusStop{} + routeStops := map[string]map[string]map[int]*BusStop{} - var headers []string - for i, line := range entries { - if i == 0 { - headers = line - continue - } + var headers []string + for i, line := range entries { + if i == 0 { + headers = line + continue + } - var entry BusStop + 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": - v, _ := strconv.ParseFloat( value, 64 ) - entry.StationSeq = int( v ) - 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_zh = value - case "STATION_NAME_ENG": - entry.Name_en = value - default: - return nil, fmt.Errorf( "Unknown header \"%s\"", headers[j] ) - } - } + for j, value := range line { + switch headers[j] { + case "ROUTE_ID": + entry.RouteId = value + case "DIRECTION": + entry.Direction = value + case "STATION_SEQNO": + v, _ := strconv.ParseFloat(value, 64) + entry.StationSeq = int(v) + 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_zh = value + case "STATION_NAME_ENG": + entry.Name_en = value + case "REFERENCE_ID": + entry.ReferenceId = value + default: + return nil, fmt.Errorf("Unknown header \"%s\"", headers[j]) + } + } - if busStops[ entry.StationId ] != nil { - return nil, fmt.Errorf( "Duplicated entry %+v", entry ) - } + // 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 + } - routeDir, hasKey := routeStops[ entry.RouteId ] - if !hasKey { - routeStops[ entry.RouteId ] = map[string] map[int] *BusStop{} - routeDir = routeStops[ entry.RouteId ] - } + if busStops[entry.StationId] != nil { + return nil, fmt.Errorf("Duplicated entry %+v", entry) + } - route, hasKey := routeDir[ entry.Direction ] - if !hasKey { - routeDir[ entry.Direction ] = map[int] *BusStop{} - route = routeDir[ entry.Direction ] - } + routeDir, hasKey := routeStops[entry.RouteId] + if !hasKey { + routeStops[entry.RouteId] = map[string]map[int]*BusStop{} + routeDir = routeStops[entry.RouteId] + } - _, hasKey = route[ entry.StationSeq ] - if !hasKey { - route[ entry.StationSeq ] = &entry - } + route, hasKey := routeDir[entry.Direction] + if !hasKey { + routeDir[entry.Direction] = map[int]*BusStop{} + route = routeDir[entry.Direction] + } - entry.RouteStops = &route - entry.Reload() + _, hasKey = route[entry.StationSeq] + if !hasKey { + route[entry.StationSeq] = &entry + } - busStops[ entry.StationId ] = &entry - } - return &busStops, nil + entry.RouteStops = &route + entry.Reload() + + busStops[entry.StationId] = &entry + } + return &busStops, nil } -func getBusStops() (*[] query.ISearchable, error) { +func getBusStops() (*[]query.ISearchable, 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 - } + 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 - } + buff, err := utils.CacheStream(CSV_BUSSTOPS, QUERY_FUNC, 7*24*3600) + if err != nil { + return nil, err + } - utils.ReadBOM( buff ) - busStopMap, err := readBusStopData( buff ) - if err != nil { - return nil, err - } + utils.ReadBOM(buff) + busStopMap, err := readBusStopData(buff) + if err != nil { + return nil, err + } - searchables := [] query.ISearchable{} - for _, busStop := range *busStopMap { - searchables = append( searchables, busStop ) - } + searchables := []query.ISearchable{} + for _, busStop := range *busStopMap { + searchables = append(searchables, busStop) + } - return &searchables, nil + return &searchables, nil }