diff --git a/cameras/cameras.go b/cameras/cameras.go index 63f0329..8ce8e28 100644 --- a/cameras/cameras.go +++ b/cameras/cameras.go @@ -63,7 +63,7 @@ func (c *CamerasClient) GetCameras() ([]Camera, error) { } q := req.URL.Query() - q.Add(wsdot.ParamAccessCode, c.wsdot.ApiKey) + q.Add(wsdot.ParamCamerasAccessCodeKey, c.wsdot.ApiKey) req.URL.RawQuery = q.Encode() req.Header.Set("Content-Type", "application/json") @@ -92,7 +92,7 @@ func (c *CamerasClient) GetCamera(cameraID int) (*Camera, error) { } q := req.URL.Query() - q.Add(wsdot.ParamAccessCode, c.wsdot.ApiKey) + q.Add(wsdot.ParamCamerasAccessCodeKey, c.wsdot.ApiKey) q.Add(ParamCameraID, strconv.Itoa(cameraID)) req.URL.RawQuery = q.Encode() req.Header.Set("Content-Type", "application/json") diff --git a/client.go b/client.go index d0a7fce..f00b746 100644 --- a/client.go +++ b/client.go @@ -20,7 +20,8 @@ var ( ) const ( - ParamAccessCode = "AccessCode" + ParamCamerasAccessCodeKey = "AccessCode" + ParamFerriesAccessCodeKey = "apiaccesscode" ) func NewWSDOTClient(options ...WSDOTClientOption) (*WSDOTClient, error) { diff --git a/example/main.go b/example/cameras/main.go similarity index 100% rename from example/main.go rename to example/cameras/main.go diff --git a/example/vessels/main.go b/example/vessels/main.go new file mode 100644 index 0000000..cd4879f --- /dev/null +++ b/example/vessels/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "fmt" + "os" + + "alpineworks.io/wsdot" + "alpineworks.io/wsdot/ferries" +) + +func main() { + apiKey := os.Getenv("API_KEY") + if apiKey == "" { + panic("API_KEY environment variable is required") + } + + // Create a new WSDOT client + wsdotClient, err := wsdot.NewWSDOTClient( + wsdot.WithAPIKey(apiKey), + ) + + if err != nil { + panic(err) + } + + // Create a new Ferries client + ferriesClient, err := ferries.NewFerriesClient(wsdotClient) + if err != nil { + panic(err) + } + + // Get the vessel basics + vessels, err := ferriesClient.GetVesselBasics() + if err != nil { + panic(err) + } + + if len(vessels) > 0 { + fmt.Println(vessels[0].VesselName) + fmt.Println(vessels[0].VesselID) + fmt.Println(vessels[0].Class.ClassName) + } + + // Get the vessel locations + vesselLocations, err := ferriesClient.GetVesselLocations() + if err != nil { + panic(err) + } + + if len(vesselLocations) > 0 { + fmt.Println(vesselLocations[1].VesselName) + fmt.Printf("%f°N, %f°W\n", vesselLocations[1].Latitude, vesselLocations[1].Longitude) + } +} diff --git a/ferries/ferries.go b/ferries/ferries.go new file mode 100644 index 0000000..5323358 --- /dev/null +++ b/ferries/ferries.go @@ -0,0 +1,136 @@ +package ferries + +import ( + "encoding/json" + "fmt" + "net/http" + + "alpineworks.io/wsdot" +) + +const ( + getVesselBasicsAsJsonURL = "https://www.wsdot.wa.gov/Ferries/API/Vessels/rest/vesselbasics" + getVesselLocationsAsJsonURL = "https://www.wsdot.wa.gov/Ferries/API/Vessels/rest/vessellocations" +) + +type FerriesClient struct { + wsdot *wsdot.WSDOTClient +} + +func NewFerriesClient(wsdotClient *wsdot.WSDOTClient) (*FerriesClient, error) { + if wsdotClient == nil { + return nil, wsdot.ErrNoClient + } + + return &FerriesClient{ + wsdot: wsdotClient, + }, nil +} + +type VesselBasic struct { + VesselID int `json:"VesselID"` + VesselSubjectID int `json:"VesselSubjectID"` + VesselName string `json:"VesselName"` + VesselAbbrev string `json:"VesselAbbrev"` + Class struct { + ClassID int `json:"ClassID"` + ClassSubjectID int `json:"ClassSubjectID"` + ClassName string `json:"ClassName"` + SortSeq int `json:"SortSeq"` + DrawingImg string `json:"DrawingImg"` + SilhouetteImg string `json:"SilhouetteImg"` + PublicDisplayName string `json:"PublicDisplayName"` + } `json:"Class"` + Status int `json:"Status"` + OwnedByWSF bool `json:"OwnedByWSF"` +} + +func (f *FerriesClient) GetVesselBasics() ([]VesselBasic, error) { + req, err := http.NewRequest(http.MethodGet, getVesselBasicsAsJsonURL, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %v", err) + } + + q := req.URL.Query() + q.Add(wsdot.ParamFerriesAccessCodeKey, f.wsdot.ApiKey) + req.URL.RawQuery = q.Encode() + req.Header.Set("Content-Type", "application/json") + + resp, err := f.wsdot.Client.Do(req) + if err != nil { + return nil, fmt.Errorf("error making request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + var vessels []VesselBasic + if err := json.NewDecoder(resp.Body).Decode(&vessels); err != nil { + return nil, fmt.Errorf("error decoding response: %v", err) + } + + return vessels, nil +} + +type VesselLocation struct { + VesselID int `json:"VesselID"` + VesselName string `json:"VesselName"` + Mmsi int `json:"Mmsi"` + DepartingTerminalID int `json:"DepartingTerminalID"` + DepartingTerminalName string `json:"DepartingTerminalName"` + DepartingTerminalAbbrev string `json:"DepartingTerminalAbbrev"` + ArrivingTerminalID int `json:"ArrivingTerminalID"` + ArrivingTerminalName string `json:"ArrivingTerminalName"` + ArrivingTerminalAbbrev string `json:"ArrivingTerminalAbbrev"` + Latitude float64 `json:"Latitude"` + Longitude float64 `json:"Longitude"` + Speed float64 `json:"Speed"` + Heading int `json:"Heading"` + InService bool `json:"InService"` + AtDock bool `json:"AtDock"` + LeftDock string `json:"LeftDock"` + Eta string `json:"Eta"` + EtaBasis string `json:"EtaBasis"` + ScheduledDeparture string `json:"ScheduledDeparture"` + OpRouteAbbrev []string `json:"OpRouteAbbrev"` + VesselPositionNum int `json:"VesselPositionNum"` + SortSeq int `json:"SortSeq"` + ManagedBy int `json:"ManagedBy"` + TimeStamp string `json:"TimeStamp"` + VesselWatchShutID int `json:"VesselWatchShutID"` + VesselWatchShutMsg string `json:"VesselWatchShutMsg"` + VesselWatchShutFlag string `json:"VesselWatchShutFlag"` + VesselWatchStatus string `json:"VesselWatchStatus"` + VesselWatchMsg string `json:"VesselWatchMsg"` +} + +func (f *FerriesClient) GetVesselLocations() ([]VesselLocation, error) { + req, err := http.NewRequest(http.MethodGet, getVesselLocationsAsJsonURL, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %v", err) + } + + q := req.URL.Query() + q.Add(wsdot.ParamFerriesAccessCodeKey, f.wsdot.ApiKey) + req.URL.RawQuery = q.Encode() + req.Header.Set("Content-Type", "application/json") + + resp, err := f.wsdot.Client.Do(req) + if err != nil { + return nil, fmt.Errorf("error making request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + var vessels []VesselLocation + if err := json.NewDecoder(resp.Body).Decode(&vessels); err != nil { + return nil, fmt.Errorf("error decoding response: %v", err) + } + + return vessels, nil +}