Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 103 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,103 @@
# go-lib
A template repository for Go libraries.
# WSDOT Go Client

A Go client library for accessing Washington State Department of Transportation (WSDOT) APIs. This library provides convenient access to traffic, vessel, and camera data from WSDOT's public APIs.

## Installation

```bash
go get alpineworks.io/wsdot
```

## Quick Start

```go
package main

import (
"fmt"
"os"

"alpineworks.io/wsdot"
"alpineworks.io/wsdot/vessels"
"alpineworks.io/wsdot/traffic"
)

func main() {
// Create client with API key
client, err := wsdot.NewWSDOTClient(
wsdot.WithAPIKey(os.Getenv("WSDOT_API_KEY")),
)
if err != nil {
panic(err)
}

// Use vessels API
vesselsClient, _ := vessels.NewVesselsClient(client)
stats, err := vesselsClient.GetVesselStats()
if err != nil {
panic(err)
}
fmt.Printf("Found %d vessels\n", len(stats))

// Use traffic API
trafficClient, _ := traffic.NewTrafficClient(client)
alerts, err := trafficClient.GetHighwayAlerts()
if err != nil {
panic(err)
}
fmt.Printf("Found %d highway alerts\n", len(alerts))
}
```

## API Modules

### Vessels
Ferry system data including schedules, vessel information, and real-time locations:
- Vessel statistics and accommodations
- Terminal locations and information
- Route schedules and sailing times
- Historical vessel positions
- Wait times and fares

### Traffic
Road and highway information:
- Highway alerts and incidents
- Travel times between points
- Traffic Cameras
- Traffic flow data
- Weather stations and conditions
- Mountain pass conditions
- Bridge clearances
- Border crossing wait times
- Commercial vehicle restrictions
- Toll rates

## Configuration

The client requires a WSDOT API key, which can be obtained from the [WSDOT website](https://wsdot.wa.gov/traffic/api/).

```go
client, err := wsdot.NewWSDOTClient(
wsdot.WithAPIKey("your-api-key"),
wsdot.WithHTTPClient(customHTTPClient), // optional
)
```

## Examples

See the `example/` directory for complete working examples:
- `example/vessels/` - Ferry and vessel data examples
- `example/traffic/` - Traffic and road condition examples
- `example/fares/` - Ferry fare information examples

Run examples with:
```bash
export WSDOT_API_KEY="your-api-key"
go run example/vessels/main.go
go run example/traffic/main.go -verbose
```

## Requirements

- Go 1.22 or higher
- Valid WSDOT API key
1 change: 1 addition & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var (
const (
ParamCamerasAccessCodeKey = "AccessCode"
ParamFerriesAccessCodeKey = "apiaccesscode"
ParamTrafficAccessCodeKey = "AccessCode"
)

func NewWSDOTClient(options ...WSDOTClientOption) (*WSDOTClient, error) {
Expand Down
10 changes: 5 additions & 5 deletions example/cameras/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"os"

"alpineworks.io/wsdot"
"alpineworks.io/wsdot/cameras"
"alpineworks.io/wsdot/traffic"
)

func main() {
Expand All @@ -23,14 +23,14 @@ func main() {
panic(err)
}

// Create a new Cameras client
camerasClient, err := cameras.NewCamerasClient(wsdotClient)
// Create a new Traffic client
trafficClient, err := traffic.NewTrafficClient(wsdotClient)
if err != nil {
panic(err)
}

// Get the cameras
cameras, err := camerasClient.GetCameras()
cameras, err := trafficClient.GetCameras()
if err != nil {
panic(err)
}
Expand All @@ -42,7 +42,7 @@ func main() {
}

// Get a specific camera
camera, err := camerasClient.GetCamera(cameras[0].CameraID)
camera, err := trafficClient.GetCamera(cameras[0].CameraID)
if err != nil {
panic(err)
}
Expand Down
230 changes: 230 additions & 0 deletions example/fares/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
package main

import (
"encoding/json"
"flag"
"fmt"
"os"
"strconv"
"time"

"alpineworks.io/wsdot"
"alpineworks.io/wsdot/vessels"
)

// prettyPrint outputs a struct as indented JSON if verbose is true
func prettyPrint(v interface{}, verbose bool, label string) {
if verbose {
fmt.Printf("\n--- %s (Full JSON) ---\n", label)
jsonBytes, err := json.MarshalIndent(v, "", " ")
if err != nil {
fmt.Printf("Error marshaling JSON: %v\n", err)
return
}
fmt.Println(string(jsonBytes))
fmt.Println("--- End JSON ---")
}
}

func main() {
// Parse command line flags
verbose := flag.Bool("verbose", false, "Print full JSON structures for all responses")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [options]\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Options:\n")
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\nExample:\n")
fmt.Fprintf(os.Stderr, " %s # Run with summary output\n", os.Args[0])
fmt.Fprintf(os.Stderr, " %s -verbose # Run with full JSON output\n", os.Args[0])
}
flag.Parse()

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 single unified vessels client that handles vessels, terminals, and fares
vesselsClient, err := vessels.NewVesselsClient(wsdotClient)
if err != nil {
panic(err)
}

fmt.Println("=== Testing Unified Vessels Client (Terminals and Fares) ===")

// 1. Test Cache Flush Date
fmt.Println("\n1. Cache Flush Date:")
cacheDate, err := vesselsClient.GetCacheFlushDate()
if err != nil {
fmt.Printf("Error getting cache flush date: %v\n", err)
} else {
fmt.Printf("Cache flushed at: %v\n", cacheDate)
prettyPrint(cacheDate, *verbose, "Cache Flush Date")
}

// 2. Test Terminal Basics
fmt.Println("\n2. Terminal Basics:")
terminalBasics, err := vesselsClient.GetTerminalBasics()
if err != nil {
fmt.Printf("Error getting terminal basics: %v\n", err)
} else {
fmt.Printf("Found %d terminals\n", len(terminalBasics))
if len(terminalBasics) > 0 {
fmt.Printf("First terminal: %s (%s) - Reservable: %t\n",
terminalBasics[0].TerminalName, terminalBasics[0].TerminalAbbrev,
terminalBasics[0].Reservable)
}
prettyPrint(terminalBasics, *verbose, "Terminal Basics")
}

// 3. Test Terminal Wait Times
fmt.Println("\n3. Terminal Wait Times:")
waitTimes, err := vesselsClient.GetTerminalWaitTimes()
if err != nil {
fmt.Printf("Error getting terminal wait times: %v\n", err)
} else {
fmt.Printf("Found %d terminal wait times\n", len(waitTimes))
for i, waitTime := range waitTimes {
if i >= 3 { // Show only first 3 to avoid too much output
fmt.Printf("... and %d more terminals\n", len(waitTimes)-3)
break
}
fmt.Printf("- %s (%s): %d minutes\n",
waitTime.TerminalName, waitTime.TerminalAbbrev, waitTime.WaitTime)
}
prettyPrint(waitTimes, *verbose, "Terminal Wait Times")
}

// 4. Test Terminal Locations
fmt.Println("\n4. Terminal Locations:")
locations, err := vesselsClient.GetTerminalLocations()
if err != nil {
fmt.Printf("Error getting terminal locations: %v\n", err)
} else {
fmt.Printf("Found %d terminal locations\n", len(locations))
if len(locations) > 0 {
fmt.Printf("Example: %s at %.4f°N, %.4f°W\n",
locations[0].TerminalName, locations[0].Latitude, locations[0].Longitude)
}
prettyPrint(locations, *verbose, "Terminal Locations")
}

// 5. Test Fare Terminals (using tomorrow's date)
fmt.Println("\n5. Fare Terminals:")
tomorrow := time.Now().AddDate(0, 0, 1).Format("2006-01-02")
fareTerminals, err := vesselsClient.GetTerminals(tomorrow)
if err != nil {
fmt.Printf("Error getting fare terminals: %v\n", err)
} else {
fmt.Printf("Found %d fare terminals for %s\n", len(fareTerminals), tomorrow)
if len(fareTerminals) > 0 {
fmt.Printf("Example: %s (%s) - Region: %d\n",
fareTerminals[0].TerminalName, fareTerminals[0].TerminalAbbrev,
fareTerminals[0].Region)
}
prettyPrint(fareTerminals, *verbose, "Fare Terminals")
}

// 7. Test Terminal Mates (if we have terminals)
if len(fareTerminals) > 0 {
fmt.Println("\n7. Terminal Mates:")
firstTerminalID := fareTerminals[0].TerminalID
mates, err := vesselsClient.GetTerminalMates(tomorrow, firstTerminalID)
if err != nil {
fmt.Printf("Error getting terminal mates for terminal %d: %v\n", firstTerminalID, err)
} else {
fmt.Printf("Terminal %s can reach %d destinations\n",
fareTerminals[0].TerminalName, len(mates))
for i, mate := range mates {
if i >= 3 { // Show only first 3
fmt.Printf("... and %d more destinations\n", len(mates)-3)
break
}
fmt.Printf("- %s (%s)\n", mate.TerminalName, mate.TerminalAbbrev)
}
prettyPrint(mates, *verbose, "Terminal Mates")
}

// 8. Test Fare Line Items (if we have terminal mates)
if len(mates) > 0 {
fmt.Println("\n8. Fare Line Items:")
departingID := strconv.Itoa(firstTerminalID)
arrivingID := strconv.Itoa(mates[0].TerminalID)

fareItems, err := vesselsClient.GetFareLineItemsBasic(tomorrow, departingID, arrivingID, false)
if err != nil {
fmt.Printf("Error getting fare line items: %v\n", err)
} else {
fmt.Printf("Found %d fare options from %s to %s\n",
len(fareItems), fareTerminals[0].TerminalName, mates[0].TerminalName)
for i, item := range fareItems {
if i >= 5 { // Show only first 5
fmt.Printf("... and %d more fare options\n", len(fareItems)-5)
break
}
fmt.Printf("- %s: $%.2f\n", item.FareLineItemDescription, item.Cost)
}
prettyPrint(fareItems, *verbose, "Fare Line Items")

// 9. Test Fare Total (for first fare item)
if len(fareItems) > 0 {
fmt.Println("\n9. Fare Total Calculation:")
fareTotal, err := vesselsClient.GetFareTotal(tomorrow, departingID, arrivingID, false,
fareItems[0].FareLineItemID, 1)
if err != nil {
fmt.Printf("Error getting fare total: %v\n", err)
} else {
fmt.Printf("Total cost for 1x %s: $%.2f\n",
fareTotal.Description, fareTotal.TotalCost)
prettyPrint(fareTotal, *verbose, "Fare Total")
}
}
}
}
}

// 10. Test specific terminal by ID (if we have terminals)
if len(terminalBasics) > 0 {
fmt.Println("\n10. Specific Terminal Details:")
terminalID := terminalBasics[0].TerminalID

// Get specific terminal basic info
terminalDetail, err := vesselsClient.GetTerminalBasicByID(terminalID)
if err != nil {
fmt.Printf("Error getting terminal details for ID %d: %v\n", terminalID, err)
} else {
fmt.Printf("Terminal %s details: %s, %s %s\n",
terminalDetail.TerminalName, terminalDetail.City,
terminalDetail.State, terminalDetail.ZipCode)
}

// Get specific terminal wait time
terminalWaitTime, err := vesselsClient.GetTerminalWaitTimeByID(terminalID)
if err != nil {
fmt.Printf("Error getting terminal wait time for ID %d: %v\n", terminalID, err)
} else {
fmt.Printf("Current wait time at %s: %d minutes\n",
terminalWaitTime.TerminalName, terminalWaitTime.WaitTime)
}

// Get specific terminal location
terminalLocation, err := vesselsClient.GetTerminalLocationByID(terminalID)
if err != nil {
fmt.Printf("Error getting terminal location for ID %d: %v\n", terminalID, err)
} else {
fmt.Printf("Terminal %s coordinates: %.4f°N, %.4f°W\n",
terminalLocation.TerminalName, terminalLocation.Latitude, terminalLocation.Longitude)
}
}

fmt.Println("\n=== All unified vessels client tests completed! ===")
}
Loading