From a57a595259d378da272002fd63831fbbc37e74cb Mon Sep 17 00:00:00 2001 From: Jay Zhang Date: Fri, 4 Sep 2020 09:22:53 +0000 Subject: [PATCH] feat: add parser for .osrm.restrictions and .osrm.cnbg_to_ebg --- integration/cmd/osrm-files-extractor/main.go | 10 + .../osrmfiles/dotcnbgtoebg/contents.go | 78 +++++ .../osrmfiles/dotrestrictions/contents.go | 75 +++++ .../conditional/condtional_turn_penalty.go | 153 ++++++++++ .../condtional_turn_penalty_test.go | 191 +++++++++++++ .../osrmtype/conditional/opening_hours.go | 269 ++++++++++++++++++ integration/osrmfiles/osrmtype/coordinate.go | 14 +- .../osrmfiles/osrmtype/coordinate_test.go | 2 +- integration/osrmfiles/osrmtype/nbg_to_ebg.go | 41 +++ .../osrmfiles/osrmtype/nbg_to_ebg_test.go | 53 ++++ 10 files changed, 878 insertions(+), 8 deletions(-) create mode 100644 integration/osrmfiles/dotcnbgtoebg/contents.go create mode 100644 integration/osrmfiles/dotrestrictions/contents.go create mode 100644 integration/osrmfiles/osrmtype/conditional/condtional_turn_penalty.go create mode 100644 integration/osrmfiles/osrmtype/conditional/condtional_turn_penalty_test.go create mode 100644 integration/osrmfiles/osrmtype/conditional/opening_hours.go create mode 100644 integration/osrmfiles/osrmtype/nbg_to_ebg.go create mode 100644 integration/osrmfiles/osrmtype/nbg_to_ebg_test.go diff --git a/integration/cmd/osrm-files-extractor/main.go b/integration/cmd/osrm-files-extractor/main.go index 2f735ade91f..c2d0eeef594 100644 --- a/integration/cmd/osrm-files-extractor/main.go +++ b/integration/cmd/osrm-files-extractor/main.go @@ -8,10 +8,12 @@ import ( "github.com/Telenav/osrm-backend/integration/osrmfiles" "github.com/Telenav/osrm-backend/integration/osrmfiles/dotcnbg" + "github.com/Telenav/osrm-backend/integration/osrmfiles/dotcnbgtoebg" "github.com/Telenav/osrm-backend/integration/osrmfiles/dotnames" "github.com/Telenav/osrm-backend/integration/osrmfiles/dotnbgnodes" "github.com/Telenav/osrm-backend/integration/osrmfiles/dotosrm" "github.com/Telenav/osrm-backend/integration/osrmfiles/dotproperties" + "github.com/Telenav/osrm-backend/integration/osrmfiles/dotrestrictions" "github.com/Telenav/osrm-backend/integration/osrmfiles/dottimestamp" "github.com/golang/glog" @@ -34,6 +36,12 @@ const ( dotCNBGSuffix = ".cnbg" dotOSRMDotCNBGSuffix = dotOSRMSuffix + dotCNBGSuffix + + dotCNBGToEBGSuffix = ".cnbg_to_ebg" + dotOSRMDotCNBGToEBGSuffix = dotOSRMSuffix + dotCNBGToEBGSuffix + + dotRestrictionsSuffix = ".restrictions" + dotOSRMDotRestrictionsSuffix = dotOSRMSuffix + dotRestrictionsSuffix ) // osrmBasefilePath should be 'xxx.osrm' @@ -46,6 +54,8 @@ func createEmptyOSRMFilesContents(osrmBasefilePath string) map[string]osrmfiles. m[dotOSRMDotPropertiesSuffix] = dotproperties.New(osrmBasefilePath + dotPropertiesSuffix) m[dotOSRMDotNamesSuffix] = dotnames.New(osrmBasefilePath + dotNamesSuffix) m[dotOSRMDotCNBGSuffix] = dotcnbg.New(osrmBasefilePath + dotCNBGSuffix) + m[dotOSRMDotCNBGToEBGSuffix] = dotcnbgtoebg.New(osrmBasefilePath + dotCNBGToEBGSuffix) + m[dotOSRMDotRestrictionsSuffix] = dotrestrictions.New(osrmBasefilePath + dotRestrictionsSuffix) return m } diff --git a/integration/osrmfiles/dotcnbgtoebg/contents.go b/integration/osrmfiles/dotcnbgtoebg/contents.go new file mode 100644 index 00000000000..f14f9bc2760 --- /dev/null +++ b/integration/osrmfiles/dotcnbgtoebg/contents.go @@ -0,0 +1,78 @@ +package dotcnbgtoebg + +import ( + "fmt" + "io" + + "github.com/Telenav/osrm-backend/integration/osrmfiles/meta" + "github.com/Telenav/osrm-backend/integration/osrmfiles/osrmtype" + + "github.com/Telenav/osrm-backend/integration/osrmfiles/fingerprint" + "github.com/golang/glog" +) + +// Contents represents `.osrm.cnbg_to_ebg` file structure. +type Contents struct { + Fingerprint fingerprint.Fingerprint + NBGToEBGsMeta meta.Num + osrmtype.NBGToEBGs + + // for internal implementation + writers map[string]io.Writer + filePath string +} + +// New creates an empty Contents for `.osrm.cnbg_to_ebg`. +func New(file string) *Contents { + c := Contents{} + + c.filePath = file + + // init writers + c.writers = map[string]io.Writer{} + c.writers["osrm_fingerprint.meta"] = &c.Fingerprint + c.writers["/common/cnbg_to_ebg.meta"] = &c.NBGToEBGsMeta + c.writers["/common/cnbg_to_ebg"] = &c.NBGToEBGs + + return &c +} + +// PrintSummary prints summary and head lines of contents. +func (c *Contents) PrintSummary(head int) { + glog.Infof("Loaded from %s\n", c.filePath) + glog.Infof(" %s\n", &c.Fingerprint) + + glog.Infof(" NBGToEBGs meta %d count %d\n", c.NBGToEBGsMeta, len(c.NBGToEBGs)) + for i := 0; i < head && i < len(c.NBGToEBGs); i++ { + glog.Infof(" NBGToEBGs[%d] %+v", i, c.NBGToEBGs[i]) + } + +} + +// Validate checks whether the contents valid or not. +func (c *Contents) Validate() error { + if !c.Fingerprint.IsValid() { + return fmt.Errorf("invalid fingerprint %v", c.Fingerprint) + } + if uint64(c.NBGToEBGsMeta) != uint64(len(c.NBGToEBGs)) { + return fmt.Errorf("NBGToEBGs meta not match, count in meta %d, but actual count %d", c.NBGToEBGsMeta, len(c.NBGToEBGs)) + } + + return nil +} + +// PostProcess post process the conents once contents loaded if necessary. +func (c *Contents) PostProcess() error { + return nil +} + +// FindWriter find io.Writer for the specified name. +func (c *Contents) FindWriter(name string) (io.Writer, bool) { + w, b := c.writers[name] + return w, b +} + +// FilePath returns the file path that stores the contents. +func (c *Contents) FilePath() string { + return c.filePath +} diff --git a/integration/osrmfiles/dotrestrictions/contents.go b/integration/osrmfiles/dotrestrictions/contents.go new file mode 100644 index 00000000000..bf8e3709f2b --- /dev/null +++ b/integration/osrmfiles/dotrestrictions/contents.go @@ -0,0 +1,75 @@ +package dotrestrictions + +import ( + "fmt" + "io" + + "github.com/Telenav/osrm-backend/integration/osrmfiles/osrmtype/conditional" + + "github.com/Telenav/osrm-backend/integration/osrmfiles/fingerprint" + "github.com/Telenav/osrm-backend/integration/osrmfiles/meta" + "github.com/golang/glog" +) + +// Contents represents `.osrm.restrictions` file structure. +type Contents struct { + Fingerprint fingerprint.Fingerprint + ConditionalTurnPenaltiesMeta meta.Num + ConditionalTurnPenalties conditional.TurnPenalties + + // for internal implementation + writers map[string]io.Writer + filePath string +} + +// New creates an empty Contents for `.osrm.restrictions`. +func New(file string) *Contents { + c := Contents{} + + c.filePath = file + + // init writers + c.writers = map[string]io.Writer{} + c.writers["osrm_fingerprint.meta"] = &c.Fingerprint + c.writers["/common/conditional_restrictions.meta"] = &c.ConditionalTurnPenaltiesMeta + c.writers["/common/conditional_restrictions"] = &c.ConditionalTurnPenalties + + return &c +} + +// PrintSummary prints summary and head lines of contents. +func (c *Contents) PrintSummary(head int) { + glog.Infof("Loaded from %s\n", c.filePath) + glog.Infof(" %s\n", &c.Fingerprint) + + glog.Infof(" ConditionalTurnPenalties meta %d (bytes)\n", c.ConditionalTurnPenaltiesMeta) + glog.Infof(" ConditionalTurnPenalties count %d\n", len(c.ConditionalTurnPenalties.TurnPenalties)) + for i := 0; i < head && i < len(c.ConditionalTurnPenalties.TurnPenalties); i++ { + glog.Infof(" ConditionalTurnPenalties[%d] %+v", i, c.ConditionalTurnPenalties.TurnPenalties[i]) + } +} + +// Validate checks whether the contents valid or not. +func (c *Contents) Validate() error { + if !c.Fingerprint.IsValid() { + return fmt.Errorf("invalid fingerprint %v", c.Fingerprint) + } + + return c.ConditionalTurnPenalties.Validate(uint64(c.ConditionalTurnPenaltiesMeta)) +} + +// PostProcess post process the conents once contents loaded if necessary. +func (c *Contents) PostProcess() error { + return nil +} + +// FindWriter find io.Writer for the specified name. +func (c *Contents) FindWriter(name string) (io.Writer, bool) { + w, b := c.writers[name] + return w, b +} + +// FilePath returns the file path that stores the contents. +func (c *Contents) FilePath() string { + return c.filePath +} diff --git a/integration/osrmfiles/osrmtype/conditional/condtional_turn_penalty.go b/integration/osrmfiles/osrmtype/conditional/condtional_turn_penalty.go new file mode 100644 index 00000000000..795d82879d6 --- /dev/null +++ b/integration/osrmfiles/osrmtype/conditional/condtional_turn_penalty.go @@ -0,0 +1,153 @@ +// Package conditional implements the C++ `struct ConditionalTurnPenalty` that used to store conditonal restrictions which has been compiled by `osrm-extract`. +// C++ implementation: https://github.com/Telenav/osrm-backend/blob/f45ab75cf9eb57cb9c857ea564beb95be0523968/include/extractor/conditional_turn_penalty.hpp#L17 +package conditional + +import ( + "encoding/binary" + "fmt" + + "github.com/Telenav/osrm-backend/integration/osrmfiles/meta" + "github.com/Telenav/osrm-backend/integration/osrmfiles/osrmtype" + "github.com/golang/glog" +) + +// TurnPenalties represents vector of TurnPenalty. +type TurnPenalties struct { + TurnPenalties []TurnPenalty + + turnPenaltiesCount meta.Num + + // The `Write` will be called many times in `io.Copy` due to fixed buffer size. + // Defaultly the copy buffer size is 32*1024 bytes, see line 391 `copyBuffer()` in https://golang.org/src/io/io.go. + // So we don't know whether we have sufficient data in parsing. + // We have to cache remain bytes if any fail occurs, and try next time to see whether data is sufficient. + unwritten []byte + + totalParsedBytes uint64 +} + +// New creates conditional turn penalties. +func New() TurnPenalties { + return TurnPenalties{ + TurnPenalties: []TurnPenalty{}, + unwritten: []byte{}, + } +} + +// TurnPenalty represents the C++ `struct ConditionalTurnPenalty`. +// C++ implementation: https://github.com/Telenav/osrm-backend/blob/f45ab75cf9eb57cb9c857ea564beb95be0523968/include/extractor/conditional_turn_penalty.hpp#L17 +type TurnPenalty struct { + TurnOffset uint64 + osrmtype.Coordinate + + conditionsCount meta.Num + + Conditions []OpeningHours +} + +func newTurnPenalty() TurnPenalty { + return TurnPenalty{ + Conditions: []OpeningHours{}, + } +} + +// Validate validates whether expected bytes were processed by parsing successfully. +func (t TurnPenalties) Validate(expectedBytes uint64) error { + if expectedBytes != t.totalParsedBytes { + return fmt.Errorf("bytes not match, expect %d but actually %d", expectedBytes, t.totalParsedBytes) + } + if len(t.unwritten) > 0 { + return fmt.Errorf("%d bytes have not been parsed", len(t.unwritten)) + } + + return nil +} + +func (t *TurnPenalties) Write(p []byte) (int, error) { + + writeLen := len(p) // always return len(p) since all data will be cached even they may have not been parsed + + t.unwritten = append(t.unwritten, p...) + writeP := t.unwritten + + if t.totalParsedBytes == 0 { // parse count of conditional turn penalties at the beginning + n, err := t.turnPenaltiesCount.Write(writeP) + if err != nil { + glog.Warning(err) + return writeLen, nil // cached all unwritten data + } + writeP = writeP[n:] + t.totalParsedBytes += uint64(n) + } + + for { + if len(writeP) == 0 { + break + } + + if len(t.TurnPenalties) == int(t.turnPenaltiesCount) { // all data has been parsed + break + } + + // write each conditional turn penalty + // the data will be cached if it fails, because it mostly caused by insufficient data and will be succeed in next time. + tp := newTurnPenalty() + n, err := tp.Write(writeP) + if err != nil { + glog.V(2).Infof("New turn penalty error: \"%v\" due to incomplete go-lang IO buffer, will continue Write in next round.", err) + break + } + t.TurnPenalties = append(t.TurnPenalties, tp) + + writeP = writeP[n:] + t.totalParsedBytes += uint64(n) + } + + t.unwritten = writeP + return writeLen, nil +} + +const ( + turnOffsetBytes = 8 // uint64 +) + +func (t *TurnPenalty) Write(p []byte) (int, error) { + if len(p) < turnOffsetBytes+osrmtype.CoordinateBytes { + return 0, fmt.Errorf("%T byte array len %d insufficient, requires at least %d bytes", t, len(p), turnOffsetBytes+osrmtype.CoordinateBytes) + } + + var writeLen int + writeP := p + + t.TurnOffset = binary.LittleEndian.Uint64(writeP) + writeP = writeP[turnOffsetBytes:] + writeLen += turnOffsetBytes + + // the order is different with normal coordinate serialization, i.e., "Lat Lon" vs. "Lon Lat" + t.Coordinate.FixedLat = osrmtype.FixedLat(binary.LittleEndian.Uint32(writeP)) + t.Coordinate.FixedLon = osrmtype.FixedLon(binary.LittleEndian.Uint32(writeP[osrmtype.FixedLatBytes:])) + writeP = writeP[osrmtype.CoordinateBytes:] + writeLen += osrmtype.CoordinateBytes + + n, err := t.conditionsCount.Write(writeP) + if err != nil { + return 0, err + } + writeP = writeP[n:] + writeLen += n + + // write opening hours + for i := 0; i < int(t.conditionsCount); i++ { + oh := newOpenningHours() + n, err := oh.Write(writeP) + if err != nil { + return 0, err + } + t.Conditions = append(t.Conditions, oh) + + writeP = writeP[n:] + writeLen += n + } + + return writeLen, nil +} diff --git a/integration/osrmfiles/osrmtype/conditional/condtional_turn_penalty_test.go b/integration/osrmfiles/osrmtype/conditional/condtional_turn_penalty_test.go new file mode 100644 index 00000000000..3c27427c319 --- /dev/null +++ b/integration/osrmfiles/osrmtype/conditional/condtional_turn_penalty_test.go @@ -0,0 +1,191 @@ +package conditional + +import ( + "reflect" + "testing" + + "github.com/Telenav/osrm-backend/integration/osrmfiles/osrmtype" +) + +func TestTurnPenaltiesWrite(t *testing.T) { + + cases := []struct { + p []byte + expectParseBytes uint64 + TurnPenalties + }{ + { + []byte{ + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0x08, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xca, 0xa5, 0x28, 0x02, 0x16, 0x67, 0x21, 0xf9, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0e, 0x14, 0xbb, + 0xa2, 0x03, 0x00, 0x00, 0x01, 0x0e, 0x14, 0xbb, 0x56, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7a, 0x09, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xa5, 0x28, 0x02, + 0x0c, 0x67, 0x21, 0xf9, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0e, 0x14, 0xbb, 0xa2, 0x03, 0x00, 0x00, + 0x01, 0x0e, 0x14, 0xbb, 0x56, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3e, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + 160, + TurnPenalties{ + []TurnPenalty{ + TurnPenalty{ + TurnOffset: 1378540, + Coordinate: osrmtype.Coordinate{FixedLon: -115251434, FixedLat: 36218314}, + conditionsCount: 1, + Conditions: []OpeningHours{ + { + Modifier: 1, + timesCount: 1, + Times: []TimeSpan{ + { + FromEvent: 1, + FromMinutes: 930, + ToEvent: 1, + ToMinutes: 1110, + }, + }, + weekdaysCount: 1, + Weekdays: []WeekdayRange{ + { + Weekydays: 62, + OvernightWeekdays: 124, + }, + }, + monthdaysCount: 0, + Monthdays: []MonthdayRange{}, + }, + }, + }, + TurnPenalty{ + TurnOffset: 1378682, + Coordinate: osrmtype.Coordinate{FixedLon: -115251444, FixedLat: 36218131}, + conditionsCount: 1, + Conditions: []OpeningHours{ + { + Modifier: 1, + timesCount: 1, + Times: []TimeSpan{ + { + FromEvent: 1, + FromMinutes: 930, + ToEvent: 1, + ToMinutes: 1110, + }, + }, + weekdaysCount: 1, + Weekdays: []WeekdayRange{ + { + Weekydays: 62, + OvernightWeekdays: 124, + }, + }, + monthdaysCount: 0, + Monthdays: []MonthdayRange{}, + }, + }, + }, + }, + 2, + []byte{}, + 160, + }, + }, + { + []byte{ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x96, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x59, 0xfe, 0x87, 0x02, 0x16, 0x7d, 0x0b, 0xfb, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x6e, 0x79, 0xe3, + 0xa4, 0x01, 0x00, 0x00, 0x01, 0x6e, 0x79, 0xe3, 0xe0, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x6e, 0x79, 0xe3, 0x48, 0x03, 0x00, 0x00, 0x01, 0x6e, 0x79, 0xe3, 0x84, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + 144, + TurnPenalties{ + []TurnPenalty{ + TurnPenalty{ + TurnOffset: 12097213, + Coordinate: osrmtype.Coordinate{FixedLon: -83133162, FixedLat: 42466905}, + conditionsCount: 2, + Conditions: []OpeningHours{ + { + Modifier: 1, + timesCount: 1, + Times: []TimeSpan{ + { + FromEvent: 1, + FromMinutes: 420, + ToEvent: 1, + ToMinutes: 480, + }, + }, + weekdaysCount: 1, + Weekdays: []WeekdayRange{ + { + Weekydays: 62, + OvernightWeekdays: 124, + }, + }, + monthdaysCount: 1, + Monthdays: []MonthdayRange{ + { + From: Monthday{ + Year: 0, + Month: 9, + Day: 0, + }, + To: Monthday{ + Year: 0, + Month: 6, + Day: 0, + }, + }, + }, + }, + { + Modifier: 1, + timesCount: 1, + Times: []TimeSpan{ + { + FromEvent: 1, + FromMinutes: 840, + ToEvent: 1, + ToMinutes: 900, + }, + }, + weekdaysCount: 0, + Weekdays: []WeekdayRange{}, + monthdaysCount: 0, + Monthdays: []MonthdayRange{}, + }, + }, + }, + }, + 1, + []byte{}, + 144, + }, + }, + } + + for _, c := range cases { + conditionalTurnPenalties := New() + _, err := conditionalTurnPenalties.Write(c.p) + if err != nil { + t.Error(err) + } + + err = conditionalTurnPenalties.Validate(c.expectParseBytes) + if err != nil { + t.Error(err) + } + + if !reflect.DeepEqual(conditionalTurnPenalties, c.TurnPenalties) { + t.Errorf("Write TurnPenalties, expect %+v but got %+v", c.TurnPenalties, conditionalTurnPenalties) + } + } +} diff --git a/integration/osrmfiles/osrmtype/conditional/opening_hours.go b/integration/osrmfiles/osrmtype/conditional/opening_hours.go new file mode 100644 index 00000000000..6f89cd45adf --- /dev/null +++ b/integration/osrmfiles/osrmtype/conditional/opening_hours.go @@ -0,0 +1,269 @@ +package conditional + +import ( + "encoding/binary" + "fmt" + + "github.com/Telenav/osrm-backend/integration/osrmfiles/meta" +) + +// OpeningHours represent "opening hours" format http://wiki.openstreetmap.org/wiki/Key:opening_hours +// C++ implementation: https://github.com/Telenav/osrm-backend/blob/f45ab75cf9eb57cb9c857ea564beb95be0523968/include/util/opening_hours.hpp#L24 +type OpeningHours struct { + Modifier uint32 // enum, see https://github.com/Telenav/osrm-backend/blob/f45ab75cf9eb57cb9c857ea564beb95be0523968/include/util/opening_hours.hpp#L26 + + timesCount meta.Num + Times []TimeSpan + + weekdaysCount meta.Num + Weekdays []WeekdayRange + + monthdaysCount meta.Num + Monthdays []MonthdayRange +} + +func newOpenningHours() OpeningHours { + return OpeningHours{ + Times: []TimeSpan{}, + Weekdays: []WeekdayRange{}, + Monthdays: []MonthdayRange{}, + } +} + +// TimeEvent represents `enum Event: unsigned char` type in C++ definition. +// C++ implementation: https://github.com/Telenav/osrm-backend/blob/f45ab75cf9eb57cb9c857ea564beb95be0523968/include/util/opening_hours.hpp#L37 +type TimeEvent uint8 + +// TimeSpan represents `struct TimeSpan` in C++ definition. +// C++ implementation: https://github.com/Telenav/osrm-backend/blob/f45ab75cf9eb57cb9c857ea564beb95be0523968/include/util/opening_hours.hpp#L59 +type TimeSpan struct { + FromEvent TimeEvent + FromMinutes int32 // https://github.com/Telenav/osrm-backend/blob/f45ab75cf9eb57cb9c857ea564beb95be0523968/include/util/opening_hours.hpp#L48 + + ToEvent TimeEvent + ToMinutes int32 // https://github.com/Telenav/osrm-backend/blob/f45ab75cf9eb57cb9c857ea564beb95be0523968/include/util/opening_hours.hpp#L48 +} + +// WeekdayRange represents `struct WeekdayRange` in C++ definition. +// C++ implementation: https://github.com/Telenav/osrm-backend/blob/f45ab75cf9eb57cb9c857ea564beb95be0523968/include/util/opening_hours.hpp#L92 +type WeekdayRange struct { + Weekydays int + OvernightWeekdays int +} + +// Monthday represents `struct Monthday` in C++ definition. +// C++ implementation: https://github.com/Telenav/osrm-backend/blob/f45ab75cf9eb57cb9c857ea564beb95be0523968/include/util/opening_hours.hpp#L113 +type Monthday struct { + Year int + Month int8 + Day int8 +} + +// MonthdayRange represents `struct MonthdayRange` in C++ definition. +// C++ implementation: https://github.com/Telenav/osrm-backend/blob/f45ab75cf9eb57cb9c857ea564beb95be0523968/include/util/opening_hours.hpp#L129 +type MonthdayRange struct { + From Monthday + To Monthday +} + +const ( + modifierBytes = 4 // 4 bytes enum + + timeEventBytes = 1 + timeReservedBytes = 3 // 3 bytes reserved between event ~ minutes + timeMinutesBytes = 4 + timeSpanBytes = (timeEventBytes + timeEventBytes + timeMinutesBytes) * 2 // *2: from, to + + weekdayBytes = 4 + overnightWeekdayBytes = 4 + weekdaysRangeBytes = weekdayBytes + overnightWeekdayBytes + + monthdayYearBytes = 4 + monthdayMonthBytes = 1 + monthdayDayBytes = 1 + monthdayReservedBytes = 2 // 2 bytes reserved at the end of Monthday + monthdayBytes = monthdayYearBytes + monthdayMonthBytes + monthdayDayBytes + monthdayReservedBytes + monthdaysRangeBytes = monthdayBytes * 2 // *2: from, to +) + +func (o *OpeningHours) Write(p []byte) (int, error) { + if len(p) < modifierBytes { + return 0, fmt.Errorf("%T byte array len %d insufficient, requires at least %d bytes", o, len(p), modifierBytes) + } + + var writeLen int + writeP := p + + // write modifier + o.Modifier = binary.LittleEndian.Uint32(writeP) + writeP = writeP[modifierBytes:] + writeLen += modifierBytes + + n, err := o.timesCount.Write(writeP) + if err != nil { + return 0, err + } + writeP = writeP[n:] + writeLen += n + + // write times + for i := 0; i < int(o.timesCount); i++ { + ts := TimeSpan{} + n, err := ts.Write(writeP) + if err != nil { + return 0, err + } + o.Times = append(o.Times, ts) + + writeP = writeP[n:] + writeLen += n + } + + n, err = o.weekdaysCount.Write(writeP) + if err != nil { + return 0, err + } + writeP = writeP[n:] + writeLen += n + + // write weekdays + for i := 0; i < int(o.weekdaysCount); i++ { + wr := WeekdayRange{} + n, err := wr.Write(writeP) + if err != nil { + return 0, err + } + o.Weekdays = append(o.Weekdays, wr) + + writeP = writeP[n:] + writeLen += n + } + + n, err = o.monthdaysCount.Write(writeP) + if err != nil { + return 0, err + } + writeP = writeP[n:] + writeLen += n + + // write monthdays + for i := 0; i < int(o.monthdaysCount); i++ { + mr := MonthdayRange{} + n, err := mr.Write(writeP) + if err != nil { + return 0, err + } + o.Monthdays = append(o.Monthdays, mr) + + writeP = writeP[n:] + writeLen += n + } + + return writeLen, nil +} + +func (t *TimeSpan) Write(p []byte) (int, error) { + if len(p) < timeSpanBytes { + return 0, fmt.Errorf("%T byte array len %d insufficient, requires at least %d bytes", t, len(p), timeSpanBytes) + } + + var writeLen int + writeP := p + + // from + t.FromEvent = TimeEvent(writeP[0]) + writeP = writeP[timeEventBytes:] + writeLen += timeEventBytes + + writeP = writeP[timeReservedBytes:] + writeLen += timeReservedBytes + + t.FromMinutes = int32(binary.LittleEndian.Uint32(writeP)) + writeP = writeP[timeMinutesBytes:] + writeLen += timeMinutesBytes + + // to + t.ToEvent = TimeEvent(writeP[0]) + writeP = writeP[timeEventBytes:] + writeLen += timeEventBytes + + writeP = writeP[timeReservedBytes:] + writeLen += timeReservedBytes + + t.ToMinutes = int32(binary.LittleEndian.Uint32(writeP)) + writeP = writeP[timeMinutesBytes:] + writeLen += timeMinutesBytes + + return writeLen, nil +} + +func (w *WeekdayRange) Write(p []byte) (int, error) { + if len(p) < weekdaysRangeBytes { + return 0, fmt.Errorf("%T byte array len %d insufficient, requires at least %d bytes", w, len(p), weekdaysRangeBytes) + } + + var writeLen int + writeP := p + + w.Weekydays = int(binary.LittleEndian.Uint32(writeP)) + writeP = writeP[weekdayBytes:] + writeLen += weekdayBytes + + w.OvernightWeekdays = int(binary.LittleEndian.Uint32(writeP)) + writeP = writeP[overnightWeekdayBytes:] + writeLen += overnightWeekdayBytes + + return writeLen, nil +} + +func (m *Monthday) Write(p []byte) (int, error) { + if len(p) < monthdayBytes { + return 0, fmt.Errorf("%T byte array len %d insufficient, requires at least %d bytes", m, len(p), monthdayBytes) + } + + var writeLen int + writeP := p + + m.Year = int(binary.LittleEndian.Uint32(writeP)) + writeP = writeP[monthdayYearBytes:] + writeLen += monthdayYearBytes + + m.Month = int8(writeP[0]) + writeP = writeP[monthdayMonthBytes:] + writeLen += monthdayMonthBytes + + m.Day = int8(writeP[0]) + writeP = writeP[monthdayDayBytes:] + writeLen += monthdayDayBytes + + writeP = writeP[monthdayReservedBytes:] + writeLen += monthdayReservedBytes + + return writeLen, nil + +} + +func (m *MonthdayRange) Write(p []byte) (int, error) { + if len(p) < monthdaysRangeBytes { + return 0, fmt.Errorf("%T byte array len %d insufficient, requires at least %d bytes", m, len(p), monthdaysRangeBytes) + } + + var writeLen int + writeP := p + + n, err := m.From.Write(writeP) + if err != nil { + return 0, err + } + writeP = writeP[n:] + writeLen += n + + n, err = m.To.Write(writeP) + if err != nil { + return 0, err + } + writeP = writeP[n:] + writeLen += n + + return writeLen, nil +} diff --git a/integration/osrmfiles/osrmtype/coordinate.go b/integration/osrmfiles/osrmtype/coordinate.go index 161ea9cc0ff..f91d6497b35 100644 --- a/integration/osrmfiles/osrmtype/coordinate.go +++ b/integration/osrmfiles/osrmtype/coordinate.go @@ -23,9 +23,9 @@ type Coordinate struct { type Coordinates []Coordinate const ( - fixedLonBytes = 4 // int32 - fixedLatBytes = 4 // int32 - coordinateBytes = fixedLonBytes + fixedLatBytes + FixedLonBytes = 4 // int32 + FixedLatBytes = 4 // int32 + CoordinateBytes = FixedLonBytes + FixedLatBytes ) func (c *Coordinates) Write(p []byte) (int, error) { @@ -33,18 +33,18 @@ func (c *Coordinates) Write(p []byte) (int, error) { var writeLen int writeP := p for { - if len(writeP) < coordinateBytes { + if len(writeP) < CoordinateBytes { break } var coordinate Coordinate coordinate.FixedLon = FixedLon(binary.LittleEndian.Uint32(writeP)) - coordinate.FixedLat = FixedLat(binary.LittleEndian.Uint32(writeP[fixedLonBytes:])) + coordinate.FixedLat = FixedLat(binary.LittleEndian.Uint32(writeP[FixedLonBytes:])) *c = append(*c, coordinate) - writeP = writeP[coordinateBytes:] - writeLen += coordinateBytes + writeP = writeP[CoordinateBytes:] + writeLen += CoordinateBytes } return writeLen, nil diff --git a/integration/osrmfiles/osrmtype/coordinate_test.go b/integration/osrmfiles/osrmtype/coordinate_test.go index 5913fa9fe80..ed298c0e6c3 100644 --- a/integration/osrmfiles/osrmtype/coordinate_test.go +++ b/integration/osrmfiles/osrmtype/coordinate_test.go @@ -54,7 +54,7 @@ func TestWriteCoordinates(t *testing.T) { if err != nil { t.Error(err) } - if len(c.p)-writeLen != len(c.p)%coordinateBytes { + if len(c.p)-writeLen != len(c.p)%CoordinateBytes { t.Errorf("len(p) %d but write len %d", len(c.p), writeLen) } if !reflect.DeepEqual(coordinates, c.Coordinates) { diff --git a/integration/osrmfiles/osrmtype/nbg_to_ebg.go b/integration/osrmfiles/osrmtype/nbg_to_ebg.go new file mode 100644 index 00000000000..48677af161f --- /dev/null +++ b/integration/osrmfiles/osrmtype/nbg_to_ebg.go @@ -0,0 +1,41 @@ +package osrmtype + +import "encoding/binary" + +// NBGToEBG represent mapping between the node based graph u,v nodes and the edge based graph head,tail edge ids. +// Required in the osrm-partition tool to translate from a nbg partition to a ebg partition. +// C++ implementation: https://github.com/Telenav/osrm-backend/blob/0b461183b97de493983ba44749c772719849fd3e/include/extractor/nbg_to_ebg.hpp#L13 +type NBGToEBG struct { + U, V NodeID + ForwardEBGNode, BackwardEBGNode NodeID +} + +// NBGToEBGs represents vector of NBGToEBG. +type NBGToEBGs []NBGToEBG + +const ( + nBGToEBGBytes = 16 +) + +func (n *NBGToEBGs) Write(p []byte) (int, error) { + + var writeLen int + writeP := p + for { + if len(writeP) < nBGToEBGBytes { + break + } + + var nbgToEBG NBGToEBG + nbgToEBG.U = NodeID(binary.LittleEndian.Uint32(writeP)) + nbgToEBG.V = NodeID(binary.LittleEndian.Uint32(writeP[4:])) + nbgToEBG.ForwardEBGNode = NodeID(binary.LittleEndian.Uint32(writeP[8:])) + nbgToEBG.BackwardEBGNode = NodeID(binary.LittleEndian.Uint32(writeP[12:])) + *n = append(*n, nbgToEBG) + + writeP = writeP[nBGToEBGBytes:] + writeLen += nBGToEBGBytes + } + + return writeLen, nil +} diff --git a/integration/osrmfiles/osrmtype/nbg_to_ebg_test.go b/integration/osrmfiles/osrmtype/nbg_to_ebg_test.go new file mode 100644 index 00000000000..9f33a57d10f --- /dev/null +++ b/integration/osrmfiles/osrmtype/nbg_to_ebg_test.go @@ -0,0 +1,53 @@ +package osrmtype + +import ( + "reflect" + "testing" +) + +func TestNBGToEBGsWrite(t *testing.T) { + cases := []struct { + p []byte + NBGToEBGs + }{ + { + []byte{0x0f, 0x00, 0x00, 0x00, 0x3f, 0x3f, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x63, 0x05, 0x00}, + NBGToEBGs{{U: 15, V: 671551, ForwardEBGNode: 0, BackwardEBGNode: 353067}}, + }, + { + []byte{ + 0x0f, 0x00, 0x00, 0x00, 0x3f, 0x3f, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x63, 0x05, 0x00, + 0xff, // redundant byte + }, + NBGToEBGs{{U: 15, V: 671551, ForwardEBGNode: 0, BackwardEBGNode: 353067}}, + }, + { + []byte{ + 0x0f, 0x00, 0x00, 0x00, 0x3f, 0x3f, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x63, 0x05, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // redundant bytes + }, + NBGToEBGs{{U: 15, V: 671551, ForwardEBGNode: 0, BackwardEBGNode: 353067}}, + }, + { + []byte{ + 0x0f, 0x00, 0x00, 0x00, 0x3f, 0x3f, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x63, 0x05, 0x00, + 0x3f, 0x3f, 0x08, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x74, 0x1a, 0x04, 0x00, 0x3f, 0x3f, 0x3f, 0x3f, + }, + NBGToEBGs{ + {U: 15, V: 671551, ForwardEBGNode: 0, BackwardEBGNode: 353067}, + {U: 540479, V: 15, ForwardEBGNode: 268916, BackwardEBGNode: 1061109567}, + }, + }, + } + + for _, c := range cases { + var nbgToEBGs NBGToEBGs + _, err := nbgToEBGs.Write(c.p) + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(nbgToEBGs, c.NBGToEBGs) { + t.Errorf("construct NBGToEBG from %v, expect %v, but got %v", c.p, c.NBGToEBGs, nbgToEBGs) + } + } +}