Add RDEX
This commit is contained in:
@@ -15,16 +15,18 @@ import (
|
||||
)
|
||||
|
||||
type BBCDailyCarpoolAPI struct {
|
||||
OperatorId string
|
||||
APIKey string
|
||||
BaseURL string
|
||||
OperatorId string
|
||||
OperatorName string
|
||||
APIKey string
|
||||
BaseURL string
|
||||
}
|
||||
|
||||
func NewBBCDailyCarpoolAPI(operatorId string, api_key string, baseURL string) (*BBCDailyCarpoolAPI, error) {
|
||||
func NewBBCDailyCarpoolAPI(operatorId string, operatorName string, api_key string, baseURL string) (*BBCDailyCarpoolAPI, error) {
|
||||
return &BBCDailyCarpoolAPI{
|
||||
OperatorId: operatorId,
|
||||
APIKey: api_key,
|
||||
BaseURL: baseURL,
|
||||
OperatorId: operatorId,
|
||||
OperatorName: operatorName,
|
||||
APIKey: api_key,
|
||||
BaseURL: baseURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -95,23 +97,33 @@ func (api *BBCDailyCarpoolAPI) GetDriverJourneys(
|
||||
Currency: &r.Price.Currency,
|
||||
}
|
||||
}
|
||||
var pickupDatetime ocss.OCSSTime
|
||||
// Try to parse as Unix timestamp first
|
||||
i, err := strconv.ParseInt(*r.PickupDatetime, 10, 64)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error in string ot int conversion")
|
||||
// If not a Unix timestamp, try parsing as ISO 8601 datetime
|
||||
pd, err2 := time.Parse(time.RFC3339, *r.PickupDatetime)
|
||||
if err2 != nil {
|
||||
log.Error().Err(err2).Str("datetime", *r.PickupDatetime).Msg("error parsing pickup datetime")
|
||||
pickupDatetime = ocss.OCSSTime(departureDate)
|
||||
} else {
|
||||
pickupDatetime = ocss.OCSSTime(pd)
|
||||
}
|
||||
} else {
|
||||
pd := time.Unix(i, 0)
|
||||
pickupDatetime = ocss.OCSSTime(pd)
|
||||
}
|
||||
pd := time.Unix(i, 0)
|
||||
pickupDatetime := ocss.OCSSTime(pd)
|
||||
driverJourney := ocss.DriverJourney{
|
||||
DriverTrip: ocss.DriverTrip{
|
||||
Driver: ocss.User{
|
||||
ID: uuid.NewString(),
|
||||
Operator: api.OperatorId,
|
||||
Alias: "Utilisateur BlablacarDaily",
|
||||
Operator: api.OperatorName,
|
||||
Alias: "Nom anonymisé",
|
||||
},
|
||||
DepartureToPickupWalkingDuration: r.DepartureToPickupWalkingTime,
|
||||
DropoffToArrivalWalkingDuration: r.DropoffToArrivalWalkingTime,
|
||||
Trip: ocss.Trip{
|
||||
Operator: api.OperatorId,
|
||||
Operator: api.OperatorName,
|
||||
PassengerPickupLat: nilCheck(r.PickupLatitude),
|
||||
PassengerPickupLng: nilCheck(r.PickupLongitude),
|
||||
PassengerDropLat: nilCheck(r.DropoffLatitude),
|
||||
@@ -171,19 +183,38 @@ func blablacarDailySearch(url string, access_token string, departure_latitude fl
|
||||
log.Error().Err(err).Msg("error in BBCDaily request")
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
response := []BBCDailyResult{}
|
||||
log.Debug().
|
||||
Int("status_code", resp.StatusCode).
|
||||
Str("status", resp.Status).
|
||||
Msg("BlaBlaCarDaily API response received")
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&response)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
body, err2 := io.ReadAll(resp.Body)
|
||||
if err2 != nil {
|
||||
log.Error().Err(err2).Msg("error reading json string")
|
||||
}
|
||||
log.Error().Err(err).Bytes("resp body", body).Any("status", resp.Status).Msg("cannot read json response to blablacardaily API")
|
||||
log.Error().Err(err).Msg("error reading BBCDaily response body")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
Str("response_body", string(body)).
|
||||
Msg("BlaBlaCarDaily API raw response")
|
||||
|
||||
response := []BBCDailyResult{}
|
||||
err = json.Unmarshal(body, &response)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("resp_body", string(body)).
|
||||
Any("status", resp.Status).
|
||||
Msg("cannot parse json response from BlaBlaCarDaily API")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
Int("results_count", len(response)).
|
||||
Msg("BlaBlaCarDaily API response parsed successfully")
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
|
||||
471
libs/carpool/rdex.go
Normal file
471
libs/carpool/rdex.go
Normal file
@@ -0,0 +1,471 @@
|
||||
package carpool
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss"
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type RDEXCarpoolAPI struct {
|
||||
OperatorId string
|
||||
OperatorName string
|
||||
PublicKey string
|
||||
PrivateKey string
|
||||
BaseURL string
|
||||
}
|
||||
|
||||
func NewRDEXCarpoolAPI(operatorId string, operatorName string, apiKey string, baseURL string) (*RDEXCarpoolAPI, error) {
|
||||
// apiKey is expected to be in format "publicKey:privateKey"
|
||||
// For backward compatibility, if no colon, treat the whole string as private key
|
||||
publicKey := ""
|
||||
privateKey := apiKey
|
||||
|
||||
// Try to split if it contains colon
|
||||
parts := []rune(apiKey)
|
||||
colonIdx := -1
|
||||
for i, r := range parts {
|
||||
if r == ':' {
|
||||
colonIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if colonIdx > 0 {
|
||||
publicKey = string(parts[:colonIdx])
|
||||
privateKey = string(parts[colonIdx+1:])
|
||||
}
|
||||
|
||||
return &RDEXCarpoolAPI{
|
||||
OperatorId: operatorId,
|
||||
OperatorName: operatorName,
|
||||
PublicKey: publicKey,
|
||||
PrivateKey: privateKey,
|
||||
BaseURL: baseURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RDEX Journey response structures
|
||||
type RDEXJourney struct {
|
||||
UUID *int64 `json:"uuid"`
|
||||
Operator *string `json:"operator"`
|
||||
Origin *string `json:"origin"`
|
||||
URL *string `json:"url"`
|
||||
Driver *RDEXUser `json:"driver"`
|
||||
From *RDEXWaypoint `json:"from"`
|
||||
To *RDEXWaypoint `json:"to"`
|
||||
Distance *int64 `json:"distance"`
|
||||
Duration *int64 `json:"duration"`
|
||||
Cost *RDEXCost `json:"cost"`
|
||||
Outward *RDEXOutward `json:"outward"`
|
||||
Type *string `json:"type"`
|
||||
Frequency *string `json:"frequency"`
|
||||
}
|
||||
|
||||
type RDEXUser struct {
|
||||
UUID *int64 `json:"uuid"`
|
||||
Alias *string `json:"alias"`
|
||||
Image *string `json:"image"`
|
||||
Gender *string `json:"gender"`
|
||||
Seats *int64 `json:"seats"`
|
||||
State *int `json:"state"`
|
||||
}
|
||||
|
||||
type RDEXCar struct {
|
||||
Model *string `json:"model"`
|
||||
}
|
||||
|
||||
type RDEXWaypoint struct {
|
||||
Address *string `json:"address"`
|
||||
City *string `json:"city"`
|
||||
PostalCode *string `json:"postalcode"`
|
||||
Country *string `json:"country"`
|
||||
Latitude *string `json:"latitude"`
|
||||
Longitude *string `json:"longitude"`
|
||||
}
|
||||
|
||||
type RDEXCost struct {
|
||||
Variable *string `json:"variable"`
|
||||
}
|
||||
|
||||
type RDEXOutward struct {
|
||||
MinDate *string `json:"mindate"`
|
||||
MaxDate *string `json:"maxdate"`
|
||||
Monday *RDEXTimeRange `json:"monday"`
|
||||
}
|
||||
|
||||
type RDEXTimeRange struct {
|
||||
MinTime *string `json:"mintime"`
|
||||
MaxTime *string `json:"maxtime"`
|
||||
}
|
||||
|
||||
type RDEXJourneysResponse struct {
|
||||
Journeys RDEXJourney `json:"journeys"`
|
||||
}
|
||||
|
||||
func (api *RDEXCarpoolAPI) GetOperatorId() string {
|
||||
return api.OperatorId
|
||||
}
|
||||
|
||||
func (api *RDEXCarpoolAPI) GetDriverJourneys(
|
||||
departureLat float64,
|
||||
departureLng float64,
|
||||
arrivalLat float64,
|
||||
arrivalLng float64,
|
||||
departureDate time.Time,
|
||||
timeDelta *time.Duration,
|
||||
departureRadius int64,
|
||||
arrivalRadius int64,
|
||||
count int64,
|
||||
) ([]ocss.DriverJourney, error) {
|
||||
td := 1 * time.Hour
|
||||
if timeDelta != nil {
|
||||
td = *timeDelta
|
||||
}
|
||||
|
||||
minDate := departureDate.Add(-td)
|
||||
maxDate := departureDate.Add(td)
|
||||
|
||||
log.Info().
|
||||
Str("operator", api.OperatorId).
|
||||
Float64("departure_lat", departureLat).
|
||||
Float64("departure_lng", departureLng).
|
||||
Float64("arrival_lat", arrivalLat).
|
||||
Float64("arrival_lng", arrivalLng).
|
||||
Time("departure_date", departureDate).
|
||||
Time("min_date", minDate).
|
||||
Time("max_date", maxDate).
|
||||
Msg("RDEX GetDriverJourneys request")
|
||||
|
||||
results, err := rdexSearch(
|
||||
api.BaseURL+"/rdex/journeys",
|
||||
api.PublicKey,
|
||||
api.PrivateKey,
|
||||
departureLat,
|
||||
departureLng,
|
||||
arrivalLat,
|
||||
arrivalLng,
|
||||
minDate,
|
||||
maxDate,
|
||||
true, // driver journeys
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("operator", api.OperatorId).Msg("error in rdexSearch")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("operator", api.OperatorId).
|
||||
Int("raw_results_count", len(results)).
|
||||
Msg("RDEX search returned results")
|
||||
|
||||
journeys := []ocss.DriverJourney{}
|
||||
|
||||
for _, r := range results {
|
||||
// Skip if not a driver journey (check driver state)
|
||||
if r.Driver == nil {
|
||||
log.Debug().Msg("Skipping journey: Driver is nil")
|
||||
continue
|
||||
}
|
||||
if r.Driver.State == nil {
|
||||
log.Debug().Msg("Skipping journey: Driver.State is nil")
|
||||
continue
|
||||
}
|
||||
if *r.Driver.State != 1 {
|
||||
log.Debug().Int("state", *r.Driver.State).Msg("Skipping journey: Driver.State is not 1")
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse price from cost.variable
|
||||
var price *ocss.Price
|
||||
if r.Cost != nil && r.Cost.Variable != nil {
|
||||
// Parse the variable cost string (e.g., "0.060345") to float
|
||||
if costFloat, err := strconv.ParseFloat(*r.Cost.Variable, 64); err == nil {
|
||||
// Convert cost per km to total price (distance is in meters)
|
||||
if r.Distance != nil {
|
||||
totalPrice := costFloat * float64(*r.Distance) / 1000.0
|
||||
paying := ocss.Paying
|
||||
currency := "EUR"
|
||||
price = &ocss.Price{
|
||||
Type: &paying,
|
||||
Amount: &totalPrice,
|
||||
Currency: ¤cy,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse pickup datetime from outward
|
||||
var pickupDatetime ocss.OCSSTime
|
||||
if r.Outward != nil && r.Outward.MinDate != nil && r.Outward.Monday != nil && r.Outward.Monday.MinTime != nil {
|
||||
dateTimeStr := fmt.Sprintf("%sT%s", *r.Outward.MinDate, *r.Outward.Monday.MinTime)
|
||||
parsedTime, err := time.Parse("2006-01-02T15:04:05", dateTimeStr)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("datetime", dateTimeStr).Msg("error parsing RDEX datetime")
|
||||
pickupDatetime = ocss.OCSSTime(departureDate)
|
||||
} else {
|
||||
pickupDatetime = ocss.OCSSTime(parsedTime)
|
||||
}
|
||||
} else {
|
||||
pickupDatetime = ocss.OCSSTime(departureDate)
|
||||
}
|
||||
|
||||
// Get driver alias
|
||||
driverAlias := "Utilisateur RDEX"
|
||||
if r.Driver != nil && r.Driver.Alias != nil {
|
||||
driverAlias = *r.Driver.Alias
|
||||
}
|
||||
|
||||
// Convert duration from seconds to duration
|
||||
var duration *time.Duration
|
||||
if r.Duration != nil {
|
||||
d := time.Duration(*r.Duration) * time.Second
|
||||
duration = &d
|
||||
}
|
||||
|
||||
// Parse coordinates from string to float64
|
||||
var fromLat, fromLng, toLat, toLng float64
|
||||
if r.From != nil && r.From.Latitude != nil && r.From.Longitude != nil {
|
||||
fromLat, _ = strconv.ParseFloat(*r.From.Latitude, 64)
|
||||
fromLng, _ = strconv.ParseFloat(*r.From.Longitude, 64)
|
||||
}
|
||||
if r.To != nil && r.To.Latitude != nil && r.To.Longitude != nil {
|
||||
toLat, _ = strconv.ParseFloat(*r.To.Latitude, 64)
|
||||
toLng, _ = strconv.ParseFloat(*r.To.Longitude, 64)
|
||||
}
|
||||
|
||||
// Build pickup and drop addresses from RDEX waypoint data
|
||||
var pickupAddress, dropAddress *string
|
||||
if r.From != nil {
|
||||
if r.From.Address != nil {
|
||||
pickupAddress = r.From.Address
|
||||
} else if r.From.City != nil {
|
||||
pickupAddress = r.From.City
|
||||
}
|
||||
}
|
||||
if r.To != nil {
|
||||
if r.To.Address != nil {
|
||||
dropAddress = r.To.Address
|
||||
} else if r.To.City != nil {
|
||||
dropAddress = r.To.City
|
||||
}
|
||||
}
|
||||
|
||||
// Get available seats from driver
|
||||
var availableSeats *int64
|
||||
if r.Driver.Seats != nil {
|
||||
availableSeats = r.Driver.Seats
|
||||
}
|
||||
|
||||
// Convert UUID to string for ID
|
||||
var uuidStr *string
|
||||
if r.UUID != nil {
|
||||
s := fmt.Sprintf("%d", *r.UUID)
|
||||
uuidStr = &s
|
||||
}
|
||||
|
||||
// Build the driver journey
|
||||
driverJourney := ocss.DriverJourney{
|
||||
DriverTrip: ocss.DriverTrip{
|
||||
Driver: ocss.User{
|
||||
ID: uuid.NewString(),
|
||||
Operator: api.OperatorName,
|
||||
Alias: driverAlias,
|
||||
},
|
||||
Trip: ocss.Trip{
|
||||
Operator: api.OperatorName,
|
||||
PassengerPickupLat: fromLat,
|
||||
PassengerPickupLng: fromLng,
|
||||
PassengerDropLat: toLat,
|
||||
PassengerDropLng: toLng,
|
||||
PassengerPickupAddress: pickupAddress,
|
||||
PassengerDropAddress: dropAddress,
|
||||
Duration: nilCheck(duration),
|
||||
Distance: r.Distance,
|
||||
// JourneyPolyline is not provided by RDEX standard
|
||||
},
|
||||
},
|
||||
JourneySchedule: ocss.JourneySchedule{
|
||||
ID: uuidStr,
|
||||
PassengerPickupDate: pickupDatetime,
|
||||
WebUrl: r.URL,
|
||||
},
|
||||
AvailableSteats: availableSeats,
|
||||
Price: price,
|
||||
}
|
||||
journeys = append(journeys, driverJourney)
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("operator", api.OperatorId).
|
||||
Int("filtered_journeys_count", len(journeys)).
|
||||
Msg("RDEX GetDriverJourneys completed")
|
||||
|
||||
return journeys, nil
|
||||
}
|
||||
|
||||
func (api *RDEXCarpoolAPI) GetPassengerJourneys(
|
||||
departureLat float64,
|
||||
departureLng float64,
|
||||
arrivalLat float64,
|
||||
arrivalLng float64,
|
||||
departureDate time.Time,
|
||||
timeDelta *time.Duration,
|
||||
departureRadius int64,
|
||||
arrivalRadius int64,
|
||||
count int64,
|
||||
) ([]ocss.PassengerJourney, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func rdexSearch(baseURL string, publicKey string, privateKey string, departureLat float64, departureLng float64, arrivalLat float64, arrivalLng float64, minDate time.Time, maxDate time.Time, isDriver bool) ([]RDEXJourney, error) {
|
||||
// Build query parameters
|
||||
params := url.Values{}
|
||||
|
||||
// Set driver or passenger state based on search type
|
||||
if isDriver {
|
||||
params.Add("p[driver][state]", "1")
|
||||
params.Add("p[passenger][state]", "0")
|
||||
} else {
|
||||
params.Add("p[driver][state]", "0")
|
||||
params.Add("p[passenger][state]", "1")
|
||||
}
|
||||
|
||||
// Add geographic coordinates
|
||||
params.Add("p[from][latitude]", fmt.Sprintf("%f", departureLat))
|
||||
params.Add("p[from][longitude]", fmt.Sprintf("%f", departureLng))
|
||||
params.Add("p[to][latitude]", fmt.Sprintf("%f", arrivalLat))
|
||||
params.Add("p[to][longitude]", fmt.Sprintf("%f", arrivalLng))
|
||||
|
||||
// Add date range
|
||||
params.Add("p[outward][mindate]", minDate.Format("2006-01-02"))
|
||||
params.Add("p[outward][maxdate]", maxDate.Format("2006-01-02"))
|
||||
|
||||
// Set to punctual frequency
|
||||
params.Add("frequency", "punctual")
|
||||
|
||||
// Add timestamp
|
||||
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
params.Add("timestamp", timestamp)
|
||||
|
||||
// Add API key
|
||||
params.Add("apikey", publicKey)
|
||||
|
||||
// Calculate signature using HMAC-SHA256
|
||||
signature := calculateRDEXSignature(params, privateKey)
|
||||
params.Add("signature", signature)
|
||||
|
||||
// Build final URL
|
||||
finalURL := baseURL + "?" + params.Encode()
|
||||
|
||||
log.Debug().
|
||||
Str("url", finalURL).
|
||||
Str("public_key", publicKey).
|
||||
Bool("is_driver", isDriver).
|
||||
Msg("RDEX API request")
|
||||
|
||||
req, err := http.NewRequest("GET", finalURL, nil)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("new request issue")
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("url", baseURL).Msg("error in RDEX request")
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
log.Debug().
|
||||
Int("status_code", resp.StatusCode).
|
||||
Str("status", resp.Status).
|
||||
Msg("RDEX API response received")
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
log.Error().
|
||||
Int("status", resp.StatusCode).
|
||||
Bytes("body", body).
|
||||
Str("url", baseURL).
|
||||
Msg("RDEX API returned non-200 status")
|
||||
return nil, fmt.Errorf("RDEX API returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error reading response body")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
Str("response_body", string(body)).
|
||||
Msg("RDEX API raw response")
|
||||
|
||||
var wrappedJourneys []RDEXJourneysResponse
|
||||
err = json.Unmarshal(body, &wrappedJourneys)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("resp_body", string(body)).
|
||||
Any("status", resp.Status).
|
||||
Str("url", baseURL).
|
||||
Msg("cannot parse json response from RDEX API")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Unwrap the journeys from the response structure
|
||||
journeys := make([]RDEXJourney, 0, len(wrappedJourneys))
|
||||
for _, wrapped := range wrappedJourneys {
|
||||
journeys = append(journeys, wrapped.Journeys)
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
Int("journeys_count", len(journeys)).
|
||||
Msg("RDEX API response parsed successfully")
|
||||
|
||||
return journeys, nil
|
||||
}
|
||||
|
||||
// calculateRDEXSignature computes the HMAC-SHA256 signature for RDEX authentication
|
||||
func calculateRDEXSignature(params url.Values, privateKey string) string {
|
||||
// Get all parameter keys and sort them alphabetically
|
||||
keys := make([]string, 0, len(params))
|
||||
for key := range params {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
// Build the string to sign by concatenating sorted key=value pairs
|
||||
var signatureData string
|
||||
for _, key := range keys {
|
||||
values := params[key]
|
||||
for _, value := range values {
|
||||
signatureData += key + "=" + value
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
Str("signature_data", signatureData).
|
||||
Msg("RDEX signature calculation")
|
||||
|
||||
// Calculate HMAC-SHA256
|
||||
h := hmac.New(sha256.New, []byte(privateKey))
|
||||
h.Write([]byte(signatureData))
|
||||
signature := hex.EncodeToString(h.Sum(nil))
|
||||
|
||||
return signature
|
||||
}
|
||||
@@ -24,19 +24,27 @@ type Itinerary struct {
|
||||
|
||||
// Leg represents a single segment of a journey
|
||||
type Leg struct {
|
||||
Mode string `json:"mode"` // WALK, BUS, TRAIN, etc.
|
||||
StartTime time.Time `json:"startTime"`
|
||||
EndTime time.Time `json:"endTime"`
|
||||
Duration int `json:"duration"` // Duration in seconds
|
||||
Distance float64 `json:"distance"` // Distance in meters
|
||||
From Place `json:"from"`
|
||||
To Place `json:"to"`
|
||||
Route *Route `json:"route,omitempty"`
|
||||
AgencyName string `json:"agencyName,omitempty"`
|
||||
Headsign string `json:"headsign,omitempty"`
|
||||
RouteShortName string `json:"routeShortName,omitempty"`
|
||||
RouteColor string `json:"routeColor,omitempty"`
|
||||
RouteTextColor string `json:"routeTextColor,omitempty"`
|
||||
Mode string `json:"mode"` // WALK, BUS, TRAIN, etc.
|
||||
StartTime time.Time `json:"startTime"`
|
||||
EndTime time.Time `json:"endTime"`
|
||||
Duration int `json:"duration"` // Duration in seconds
|
||||
Distance float64 `json:"distance"` // Distance in meters
|
||||
From Place `json:"from"`
|
||||
To Place `json:"to"`
|
||||
Route *Route `json:"route,omitempty"`
|
||||
AgencyName string `json:"agencyName,omitempty"`
|
||||
Headsign string `json:"headsign,omitempty"`
|
||||
RouteShortName string `json:"routeShortName,omitempty"`
|
||||
RouteColor string `json:"routeColor,omitempty"`
|
||||
RouteTextColor string `json:"routeTextColor,omitempty"`
|
||||
LegGeometry *LegGeometry `json:"legGeometry,omitempty"`
|
||||
}
|
||||
|
||||
// LegGeometry represents the encoded polyline geometry of a leg
|
||||
type LegGeometry struct {
|
||||
Points string `json:"points"` // Encoded polyline string
|
||||
Precision int `json:"precision"` // Polyline precision (7 for v1, 6 for v2)
|
||||
Length int `json:"length"` // Number of points in the polyline
|
||||
}
|
||||
|
||||
// Place represents a location (stop, station, or coordinate)
|
||||
|
||||
Reference in New Issue
Block a user