Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

timeframes.txt, networks.txt, route_networks.txt #388

Merged
merged 13 commits into from
Dec 18, 2024
36 changes: 36 additions & 0 deletions adapters/direct/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ type Reader struct {
FareMediaList []gtfs.FareMedia
FareProductList []gtfs.FareProduct
RiderCategoryList []gtfs.RiderCategory
TimeframeList []gtfs.Timeframe
NetworkList []gtfs.Network
RouteNetworkList []gtfs.RouteNetwork
OtherList []tt.Entity
}

Expand Down Expand Up @@ -396,3 +399,36 @@ func (mr *Reader) RiderCategories() chan gtfs.RiderCategory {
}()
return out
}

func (mr *Reader) Timeframes() chan gtfs.Timeframe {
out := make(chan gtfs.Timeframe, bufferSize)
go func() {
for _, ent := range mr.TimeframeList {
out <- ent
}
close(out)
}()
return out
}

func (mr *Reader) Networks() chan gtfs.Network {
out := make(chan gtfs.Network, bufferSize)
go func() {
for _, ent := range mr.NetworkList {
out <- ent
}
close(out)
}()
return out
}

func (mr *Reader) RouteNetworks() chan gtfs.RouteNetwork {
out := make(chan gtfs.RouteNetwork, bufferSize)
go func() {
for _, ent := range mr.RouteNetworkList {
out <- ent
}
close(out)
}()
return out
}
12 changes: 12 additions & 0 deletions adapters/multireader/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,18 @@ func (mr *Reader) RiderCategories() chan gtfs.RiderCategory {
return readEntities(mr, func(r adapters.Reader) chan gtfs.RiderCategory { return r.RiderCategories() }, setFv[*gtfs.RiderCategory])
}

func (mr *Reader) Timeframes() chan gtfs.Timeframe {
return readEntities(mr, func(r adapters.Reader) chan gtfs.Timeframe { return r.Timeframes() }, setFv[*gtfs.Timeframe])
}

func (mr *Reader) Networks() chan gtfs.Network {
return readEntities(mr, func(r adapters.Reader) chan gtfs.Network { return r.Networks() }, setFv[*gtfs.Network])
}

func (mr *Reader) RouteNetworks() chan gtfs.RouteNetwork {
return readEntities(mr, func(r adapters.Reader) chan gtfs.RouteNetwork { return r.RouteNetworks() }, setFv[*gtfs.RouteNetwork])
}

type canSetFV interface {
SetFeedVersionID(int)
}
Expand Down
16 changes: 16 additions & 0 deletions causes/causes.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,22 @@ func (e *DuplicateIDError) Error() string {

////////////////////////////

// DuplicateIDError reports when a unique ID is used more than once in a file.
type DuplicateKeyError struct {
bc
}

// NewDuplicateIDError returns a new DuplicateIDErrror
func NewDuplicateKeyError(eid string) *DuplicateKeyError {
return &DuplicateKeyError{bc: bc{Value: eid}}
}

func (e *DuplicateKeyError) Error() string {
return fmt.Sprintf("entity with fields '%s' is present more than once", e.Value)
}

////////////////////////////

// DuplicateServiceExceptionError reports when a (service_id,date) value is present more than once.
type DuplicateServiceExceptionError struct {
ServiceID string
Expand Down
25 changes: 18 additions & 7 deletions copier/copier.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ type Options struct {
CopyExtraFiles bool
// Simplify shapes
SimplifyShapes float64
// Convert route network_id to networks.txt/route_networks.txt
NormalizeNetworks bool
// DeduplicateStopTimes
DeduplicateJourneyPatterns bool
// Default error handler
Expand All @@ -120,7 +122,7 @@ type Options struct {
// Named extensions
Extensions []string
// Initialized extensions
extensions []Extension
extensions []any
// Error limit
ErrorLimit int

Expand All @@ -129,7 +131,7 @@ type Options struct {
sublogger zerolog.Logger
}

func (opts *Options) AddExtension(ext Extension) {
func (opts *Options) AddExtension(ext any) {
opts.extensions = append(opts.extensions, ext)
}

Expand Down Expand Up @@ -241,16 +243,14 @@ func NewCopier(reader adapters.Reader, writer adapters.Writer, opts Options) (*C

// Default set of validators
if !opts.NoValidators {
copier.AddValidator(&rules.EntityDuplicateCheck{}, 0)
copier.AddValidator(&rules.EntityDuplicateIDCheck{}, 0)
copier.AddValidator(&rules.EntityDuplicateKeyCheck{}, 0)
copier.AddValidator(&rules.ValidFarezoneCheck{}, 0)
copier.AddValidator(&rules.AgencyIDConditionallyRequiredCheck{}, 0)
copier.AddValidator(&rules.StopTimeSequenceCheck{}, 0)
copier.AddValidator(&rules.InconsistentTimezoneCheck{}, 0)
copier.AddValidator(&rules.ParentStationLocationTypeCheck{}, 0)
copier.AddValidator(&rules.CalendarDuplicateDates{}, 0)
copier.AddValidator(&rules.DuplicateFareLegRuleCheck{}, 0)
copier.AddValidator(&rules.DuplicateFareTransferRuleCheck{}, 0)
copier.AddValidator(&rules.DuplicateFareProductCheck{}, 0)
}

// Default extensions
Expand All @@ -267,6 +267,12 @@ func NewCopier(reader adapters.Reader, writer adapters.Writer, opts Options) (*C
// Simplify shapes.txt
copier.AddExtension(&filters.SimplifyShapeFilter{SimplifyValue: copier.SimplifyShapes})
}
if copier.NormalizeNetworks {
// Convert routes.txt network_id to networks.txt/route_networks.txt
copier.AddExtension(&filters.RouteNetworkIDFilter{})
} else {
copier.AddExtension(&filters.RouteNetworkIDCompatFilter{})
}
if copier.SimplifyCalendars && copier.NormalizeServiceIDs {
// Simplify calendar and calendar dates
copier.AddExtension(&filters.SimplifyCalendarFilter{})
Expand Down Expand Up @@ -352,7 +358,9 @@ func (copier *Copier) addExtension(ext interface{}, warning bool) error {
added = true
}
if !added {
return errors.New("extension does not satisfy any extension interfaces")
err := errors.New("extension does not satisfy any extension interfaces")
log.Error().Err(err).Msg(err.Error())
return err
}
return nil
}
Expand Down Expand Up @@ -573,6 +581,9 @@ func (copier *Copier) Copy() *Result {
func() error { return batchCopy(copier, batchChan(r.FeedInfos(), bs, nil)) },
func() error { return batchCopy(copier, batchChan(r.Translations(), bs, nil)) },
func() error { return batchCopy(copier, batchChan(r.Attributions(), bs, nil)) },
func() error { return batchCopy(copier, batchChan(r.Timeframes(), bs, nil)) },
func() error { return batchCopy(copier, batchChan(r.Networks(), bs, nil)) },
func() error { return batchCopy(copier, batchChan(r.RouteNetworks(), bs, nil)) },
func() error { return batchCopy(copier, batchChan(r.Areas(), bs, nil)) },
func() error { return batchCopy(copier, batchChan(r.StopAreas(), bs, nil)) },
func() error { return batchCopy(copier, batchChan(r.RiderCategories(), bs, nil)) },
Expand Down
3 changes: 3 additions & 0 deletions dmfr/feed_version_tables.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ func GetFeedVersionTables() FeedVersionTables {
"gtfs_fare_products",
"gtfs_fare_transfer_rules",
"gtfs_rider_categories",
"gtfs_route_networks",
"gtfs_networks",
"gtfs_timeframes",
"gtfs_areas",
"gtfs_pathways",
"gtfs_fare_attributes",
Expand Down
55 changes: 55 additions & 0 deletions filters/route_network_id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package filters

import (
"github.com/interline-io/transitland-lib/gtfs"
"github.com/interline-io/transitland-lib/tt"
)

// RouteNetworkIDFilter converts routes.txt network_id into networks.txt/route_networks.txt
type RouteNetworkIDFilter struct{}

func (e *RouteNetworkIDFilter) Expand(ent tt.Entity, emap *tt.EntityMap) ([]tt.Entity, bool, error) {
// Check if route and has NetworkID set
v, ok := ent.(*gtfs.Route)
if !ok {
return nil, false, nil
}
if !v.NetworkID.Valid {
return nil, false, nil
}
// Expand into route + route_network + possible network
var ret []tt.Entity
ret = append(ret, ent)
if _, ok := emap.Get("networks.txt", v.NetworkID.Val); !ok {
n := gtfs.Network{}
n.NetworkID.Set(v.NetworkID.Val)
ret = append(ret, &n)
}
rn := gtfs.RouteNetwork{}
rn.NetworkID.Set(v.NetworkID.Val)
rn.RouteID.Set(v.RouteID.Val)
ret = append(ret, &rn)
return ret, true, nil
}

func (e *RouteNetworkIDFilter) Filter(ent tt.Entity, emap *tt.EntityMap) error {
// Unset any set NetworkID
if v, ok := ent.(*gtfs.Route); ok {
v.NetworkID = tt.String{}
}
return nil
}

////////////

// RouteNetworkIDCompatFilter copies routes.txt:network_id IDs into networks.txt:network_id
type RouteNetworkIDCompatFilter struct{}

func (e *RouteNetworkIDCompatFilter) AfterWrite(eid string, ent tt.Entity, emap *tt.EntityMap) error {
if v, ok := ent.(*gtfs.Route); ok {
if v.NetworkID.Valid {
emap.Set("networks.txt", v.NetworkID.Val, v.NetworkID.Val)
}
}
return nil
}
30 changes: 24 additions & 6 deletions gtfs/fare_leg_rule.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package gtfs

import (
"fmt"

"github.com/interline-io/transitland-lib/tt"
)

// FareLegRule fare_leg_rules.txt
type FareLegRule struct {
LegGroupID tt.String
FromAreaID tt.String `target:"areas.txt"`
ToAreaID tt.String `target:"areas.txt"`
NetworkID tt.String `target:"routes.txt:network_id"`
FareProductID tt.String `csv:",required" target:"fare_products.txt:fare_product_id"`
TransferOnly tt.Int `enum:"0,1"` // interline ext
LegGroupID tt.String
FromAreaID tt.String `target:"areas.txt"`
ToAreaID tt.String `target:"areas.txt"`
NetworkID tt.String `target:"networks.txt"`
FareProductID tt.String `csv:",required" target:"fare_products.txt:fare_product_id"`
FromTimeframeGroupID tt.String `target:"timeframes.txt:timeframe_group_id"`
ToTimeframeGroupID tt.String `target:"timeframes.txt:timeframe_group_id"`
RulePriority tt.Int `range:"0,"`
TransferOnly tt.Int `enum:"0,1"` // interline ext
tt.BaseEntity
}

Expand All @@ -30,3 +35,16 @@ func (ent *FareLegRule) TableName() string {
func (ent *FareLegRule) GroupKey() (string, string) {
return "leg_group_id", ent.LegGroupID.Val
}

func (ent *FareLegRule) DuplicateKey() string {
key := fmt.Sprintf(
"fare_product_id:'%s' network_id:'%s' from_area_id:'%s' to_area_id:'%s' from_timeframe_group_id:'%s' to_timeframe_group_id:'%s'",
ent.FareProductID.Val,
ent.NetworkID.Val,
ent.FromAreaID.Val,
ent.ToAreaID.Val,
ent.FromTimeframeGroupID.Val,
ent.ToTimeframeGroupID.Val,
)
return key
}
11 changes: 11 additions & 0 deletions gtfs/fare_product.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package gtfs

import (
"fmt"

"github.com/interline-io/transitland-lib/causes"
"github.com/interline-io/transitland-lib/tt"
)
Expand Down Expand Up @@ -54,3 +56,12 @@ func (ent *FareProduct) ConditionalErrors() (errs []error) {
}
return errs
}

func (ent *FareProduct) DuplicateKey() string {
return fmt.Sprintf(
"fare_product_id:'%s' rider_category_id:'%s' fare_media_id:'%s'",
ent.FareProductID,
ent.RiderCategoryID,
ent.FareMediaID,
)
}
12 changes: 12 additions & 0 deletions gtfs/fare_rule.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package gtfs

import (
"fmt"

"github.com/interline-io/transitland-lib/tt"
)

Expand All @@ -23,3 +25,13 @@ func (ent *FareRule) Filename() string {
func (ent *FareRule) TableName() string {
return "gtfs_fare_rules"
}

func (ent *FareRule) DuplicateKey() string {
return fmt.Sprintf(
"route_id:'%s' origin_id:'%s' destination_id:'%s' contains_id:'%s'",
ent.RouteID.Val,
ent.OriginID.Val,
ent.DestinationID.Val,
ent.ContainsID.Val,
)
}
12 changes: 12 additions & 0 deletions gtfs/fare_transfer_rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,15 @@ func (ent *FareTransferRule) ConditionalErrors() (errs []error) {
}
return errs
}

func (ent *FareTransferRule) DuplicateKey() string {
return fmt.Sprintf(
"fare_product_id:'%s' from_leg_group_id:'%s' to_leg_group_id:'%s' filter_fare_product_id:'%s' transfer_count:%d duration_limit:%d",
ent.FareProductID.Val,
ent.FromLegGroupID.Val,
ent.ToLegGroupID.Val,
ent.FilterFareProductID.Val,
ent.TransferCount.Val,
ent.DurationLimit.Val,
)
}
3 changes: 3 additions & 0 deletions gtfs/gtfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ type Reader interface {
FareProducts() chan FareProduct
RiderCategories() chan RiderCategory
FareMedia() chan FareMedia
Timeframes() chan Timeframe
Networks() chan Network
RouteNetworks() chan RouteNetwork
}
25 changes: 25 additions & 0 deletions gtfs/network.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package gtfs

import "github.com/interline-io/transitland-lib/tt"

type Network struct {
NetworkID tt.String
NetworkName tt.String
tt.BaseEntity
}

func (ent *Network) EntityKey() string {
return ent.NetworkID.Val
}

func (ent *Network) EntityID() string {
return entID(ent.ID, ent.NetworkID.Val)
}

func (ent *Network) Filename() string {
return "networks.txt"
}

func (ent *Network) TableName() string {
return "gtfs_networks"
}
Loading
Loading