diff --git a/example/build_repo_example/main.go b/example/build_repo_example/main.go index 9f33fd5..11e8302 100644 --- a/example/build_repo_example/main.go +++ b/example/build_repo_example/main.go @@ -17,49 +17,10 @@ func main() { rtssGraphicStorage := data_proto.GetXian6STYG() dataMapping := repository.NewDataMapping("1", rtssGraphicStorage) repo1.DataMapping["1"] = dataMapping - // log.Println(rtssGraphicStorage.Stations) - // log.Println(rtssGraphicStorage.Section) - // log.Println(rtssGraphicStorage.Turnouts) - for _, station := range rtssGraphicStorage.Stations { - dataMapping.StationDataMap[station.Common.Id] = station - uid := station.Code - dataMapping.AddIdMapping(repository.NewIdMapping(station.Common.Id, uid)) - stationModel := model.NewStation(uid, station.ConcentrationStations) - repo1.StationMap[uid] = &modelimpl.Station{StationImpl: stationModel} - } - for _, section := range rtssGraphicStorage.Section { - if section.CentralizedStations == nil || len(section.CentralizedStations) == 0 { - continue - } - belongStation := dataMapping.StationDataMap[section.CentralizedStations[0]] - if belongStation == nil { - continue - } - dataMapping.SectionDataMap[section.Common.Id] = section - uid := getSectionUid(section, belongStation, dataMapping.GetLineInfo()) - dataMapping.AddIdMapping(repository.NewIdMapping(section.Common.Id, uid)) - if section.SectionType == data_proto.Section_Physical { - sectionModel := model.NewPhysicalSection(uid) - repo1.PhysicalSectionMap[uid] = &modelimpl.PhysicalSection{PhysicalSectionImpl: sectionModel} - } else { - tsModel := model.NewTurnoutSection(uid) - repo1.TurnoutSectionMap[uid] = tsModel - } - } - for _, turnout := range rtssGraphicStorage.Turnouts { - if turnout.CentralizedStations == nil || len(turnout.CentralizedStations) == 0 { - continue - } - belongStation := dataMapping.StationDataMap[turnout.CentralizedStations[0]] - if belongStation == nil { - continue - } - dataMapping.TurnoutDataMap[turnout.Common.Id] = turnout - uid := getTurnoutUid(turnout, belongStation, dataMapping.GetLineInfo()) - dataMapping.AddIdMapping(repository.NewIdMapping(turnout.Common.Id, uid)) - turnoutModel := model.NewTurnout(uid) - repo1.TurnoutMap[uid] = &modelimpl.Turnout{TurnoutImpl: turnoutModel} - } + basicDataMappingAndModelBuild(dataMapping, repo1) + + // 构建公里标转换器 + buildKilometerMarkConverters(dataMapping, repo1) // 构建区段关系 buildSectionRelationships(dataMapping, repo1) @@ -92,14 +53,80 @@ func main() { } } +func basicDataMappingAndModelBuild(dataMapping *repository.DataMapping, repo1 *repository.Repository) { + // 车站 + for _, station := range dataMapping.Stations { + dataMapping.StationDataMap[station.Common.Id] = station + uid := station.Code + dataMapping.AddIdMapping(repository.NewIdMapping(station.Common.Id, uid)) + stationModel := model.NewStation(uid, station.ConcentrationStations) + repo1.StationMap[uid] = &modelimpl.Station{StationImpl: stationModel} + } + // 物理区段 + for _, section := range dataMapping.Section { + if section.CentralizedStations == nil || len(section.CentralizedStations) == 0 { + continue + } + belongStation := dataMapping.StationDataMap[section.CentralizedStations[0]] + if belongStation == nil { + continue + } + dataMapping.SectionDataMap[section.Common.Id] = section + uid := getSectionUid(section, belongStation, dataMapping.GetLineInfo()) + dataMapping.AddIdMapping(repository.NewIdMapping(section.Common.Id, uid)) + if section.SectionType == data_proto.Section_Physical { + sectionModel := model.NewPhysicalSection(uid) + repo1.PhysicalSectionMap[uid] = &modelimpl.PhysicalSection{PhysicalSectionImpl: sectionModel} + } else { + tsModel := model.NewTurnoutSection(uid) + repo1.TurnoutSectionMap[uid] = tsModel + } + } + // 道岔 + for _, turnout := range dataMapping.Turnouts { + if turnout.CentralizedStations == nil || len(turnout.CentralizedStations) == 0 { + continue + } + belongStation := dataMapping.StationDataMap[turnout.CentralizedStations[0]] + if belongStation == nil { + continue + } + dataMapping.TurnoutDataMap[turnout.Common.Id] = turnout + uid := getTurnoutUid(turnout, belongStation, dataMapping.GetLineInfo()) + dataMapping.AddIdMapping(repository.NewIdMapping(turnout.Common.Id, uid)) + turnoutModel := model.NewTurnout(uid) + repo1.TurnoutMap[uid] = &modelimpl.Turnout{TurnoutImpl: turnoutModel} + } + // 区段检测点 + for _, axleCounting := range dataMapping.AxleCountings { + dataMapping.SectionCheckPointMap[axleCounting.Common.Id] = axleCounting + } +} + +func buildKilometerMarkConverters(dataMapping *repository.DataMapping, repo1 *repository.Repository) { + if dataMapping.KilometerConvertList == nil || len(dataMapping.KilometerConvertList) == 0 { + slog.Info("没有公里标转换数据") + return + } + for _, kmConverter := range dataMapping.KilometerConvertList { + km1 := convertKs2Km(kmConverter.KmA) + km2 := convertKs2Km(kmConverter.KmB) + repo1.KilometerMarkConverters = append(repo1.KilometerMarkConverters, + model.NewKilometerMarkConverter(km1, km2, kmConverter.SameTrend)) + } +} + +func convertKs2Km(ks *data_proto.KilometerSystem) *model.KilometerMark { + return model.NewKilometerMark(ks.CoordinateSystem, ks.Kilometer) +} + // 构建区段、道岔公里标 func buildKilometerMark(dataMapping *repository.DataMapping, repo1 *repository.Repository) { for _, turnout := range dataMapping.TurnoutDataMap { if len(turnout.KilometerSystem) == 0 { repo1.BuildErrorInfos = append(repo1.BuildErrorInfos, fmt.Errorf("构建区段、道岔公里标数据错误:道岔[id=%d]未关联任何公里标", turnout.Common.Id)) } else { - tks := turnout.KilometerSystem[0] - km := model.NewKilometerMark(tks.CoordinateSystem, convertKmDirection(tks.Direction), tks.Kilometer) + km := convertKs2Km(turnout.KilometerSystem[0]) turnoutModel := repo1.TurnoutMap[dataMapping.IdMappingMap[turnout.Common.Id].Uid] turnoutModel.(*modelimpl.Turnout).Km = km } @@ -120,7 +147,7 @@ func buildKilometerMark(dataMapping *repository.DataMapping, repo1 *repository.R if !ok { panic(fmt.Errorf("构建区段、道岔公里标数据错误:检测点[id=%d]关联的区段模型[uid=%s]不存在", checkpoint.Common.Id, idmapping.Uid)) } - km := model.NewKilometerMark(checkpoint.KilometerSystem.CoordinateSystem, convertKmDirection(checkpoint.KilometerSystem.Direction), checkpoint.KilometerSystem.Kilometer) + km := convertKs2Km(checkpoint.KilometerSystem) if linkship.DevicePort == data_proto.RelatedRef_A { sectionModel.(*modelimpl.PhysicalSection).PaKm = km } else if linkship.DevicePort == data_proto.RelatedRef_B { @@ -133,7 +160,7 @@ func buildKilometerMark(dataMapping *repository.DataMapping, repo1 *repository.R if !ok { panic(fmt.Errorf("构建区段、道岔公里标数据错误:检测点[id=%d]关联的道岔模型[uid=%s]不存在", checkpoint.Common.Id, idmapping.Uid)) } - km := model.NewKilometerMark(checkpoint.KilometerSystem.CoordinateSystem, convertKmDirection(checkpoint.KilometerSystem.Direction), checkpoint.KilometerSystem.Kilometer) + km := convertKs2Km(checkpoint.KilometerSystem) if linkship.DevicePort == data_proto.RelatedRef_A { turnoutModel.(*modelimpl.Turnout).PaKm = km } else if linkship.DevicePort == data_proto.RelatedRef_B { @@ -150,16 +177,6 @@ func buildKilometerMark(dataMapping *repository.DataMapping, repo1 *repository.R } } -func convertKmDirection(kddata data_proto.KilometerSystem_Direction) model.OperationDirection { - if kddata == data_proto.KilometerSystem_LEFT { - return model.OperationDirectionDown - } - if kddata == data_proto.KilometerSystem_RIGHT { - return model.OperationDirectionUp - } - panic(fmt.Errorf("未知的公里标方向类型: %v", kddata)) -} - func buildTurnoutRelationships(dataMapping *repository.DataMapping, repo1 *repository.Repository) { for _, turnout := range dataMapping.TurnoutDataMap { idmapping := dataMapping.IdMappingMap[turnout.Common.Id] @@ -175,13 +192,41 @@ func buildTurnoutRelationships(dataMapping *repository.DataMapping, repo1 *repos func buildSectionRelationships(dataMapping *repository.DataMapping, repo1 *repository.Repository) { for _, section := range dataMapping.SectionDataMap { - idmapping := dataMapping.IdMappingMap[section.Common.Id] - if idmapping == nil { - panic(fmt.Errorf("构建区段关系错误:idmapping异常为空")) + idmapping := dataMapping.GetIdMapping(section.Common.Id) + if section.SectionType == data_proto.Section_Physical { + sectionModel := repo1.PhysicalSectionMap[idmapping.Uid] + buildSectionPortLinkRelation(section.PaRef, sectionModel, model.PipePortA, repo1, dataMapping) + buildSectionPortLinkRelation(section.PbRef, sectionModel, model.PipePortB, repo1, dataMapping) + } else { + tsModel := repo1.TurnoutSectionMap[idmapping.Uid] + turnoutPorts := make(map[uint32]map[model.PipePort]struct{}) + for _, axleId := range section.AxleCountings { + slog.Debug("区段关联检测点", "sectionId", section.Common.Id, "axleId", axleId) + axleCounting := dataMapping.SectionCheckPointMap[axleId] + if axleCounting == nil { + repo1.BuildErrorInfos = append(repo1.BuildErrorInfos, fmt.Errorf("构建区段关系错误:区段[id=%d]关联的检测点[id=%d]不存在", section.Common.Id, axleId)) + continue + } + for _, ref := range axleCounting.AxleCountingRef { + if ref.DeviceType == data_proto.RelatedRef_Turnout { + portMap := turnoutPorts[ref.Id] + if portMap == nil { + portMap = make(map[model.PipePort]struct{}) + turnoutPorts[ref.Id] = portMap + } + portMap[convertPort(ref.DevicePort)] = struct{}{} + } + } + } + slog.Debug("区段关联检测点关联道岔", "sectionId", section.Common.Id, "turnoutPorts", turnoutPorts) + for turnoutId, portMap := range turnoutPorts { + if len(portMap) == 3 { + turnoutModel := repo1.TurnoutMap[dataMapping.GetIdMapping(turnoutId).Uid] + tsModel.AddTurnout(turnoutModel.(*modelimpl.Turnout)) + } + } } - sectionModel := repo1.PhysicalSectionMap[idmapping.Uid] - buildSectionPortLinkRelation(section.PaRef, sectionModel, model.PipePortA, repo1, dataMapping) - buildSectionPortLinkRelation(section.PbRef, sectionModel, model.PipePortB, repo1, dataMapping) + } } diff --git a/example/repository/repo.go b/example/repository/repo.go index 5ca510e..697ed8f 100644 --- a/example/repository/repo.go +++ b/example/repository/repo.go @@ -23,11 +23,20 @@ func NewIdMapping(graphicId uint32, uid string) *IdMapping { type DataMapping struct { id string *data_proto.RtssGraphicStorage - StationDataMap map[uint32]*data_proto.Station - SectionDataMap map[uint32]*data_proto.Section - TurnoutDataMap map[uint32]*data_proto.Turnout - IdMappingMap map[uint32]*IdMapping - UidMappingMap map[string]*IdMapping + StationDataMap map[uint32]*data_proto.Station + SectionDataMap map[uint32]*data_proto.Section + TurnoutDataMap map[uint32]*data_proto.Turnout + SectionCheckPointMap map[uint32]*data_proto.AxleCounting + IdMappingMap map[uint32]*IdMapping + UidMappingMap map[string]*IdMapping +} + +func (d *DataMapping) GetIdMapping(graphicId uint32) *IdMapping { + mapping, ok := d.IdMappingMap[graphicId] + if !ok { + panic(fmt.Sprintf("不存在{graphicId=%v}的IdMapping数据", graphicId)) + } + return mapping } func (d *DataMapping) AddIdMapping(idMapping *IdMapping) { @@ -42,13 +51,14 @@ func (d *DataMapping) GetLineInfo() string { func NewDataMapping(id string, storage *data_proto.RtssGraphicStorage) *DataMapping { return &DataMapping{ - id: id, - RtssGraphicStorage: storage, - StationDataMap: make(map[uint32]*data_proto.Station), - SectionDataMap: make(map[uint32]*data_proto.Section), - TurnoutDataMap: make(map[uint32]*data_proto.Turnout), - IdMappingMap: make(map[uint32]*IdMapping), - UidMappingMap: make(map[string]*IdMapping), + id: id, + RtssGraphicStorage: storage, + StationDataMap: make(map[uint32]*data_proto.Station), + SectionDataMap: make(map[uint32]*data_proto.Section), + TurnoutDataMap: make(map[uint32]*data_proto.Turnout), + SectionCheckPointMap: make(map[uint32]*data_proto.AxleCounting), + IdMappingMap: make(map[uint32]*IdMapping), + UidMappingMap: make(map[string]*IdMapping), } } diff --git a/model/kilometer_mark.go b/model/kilometer_mark.go index 25f926a..26fac19 100644 --- a/model/kilometer_mark.go +++ b/model/kilometer_mark.go @@ -5,29 +5,22 @@ import ( "strconv" ) -// 运营方向(上行/下行) -type OperationDirection int8 - -const ( - // 上行 - OperationDirectionUp OperationDirection = 1 - // 下行 - OperationDirectionDown OperationDirection = 0 -) - +// 公里标 +// 类似地铁线路一般正线都是双线,公里标一般以类似YDK/ZDK开头, +// 虽然从信号布置图上看,YDK/ZDK相近位置的值似乎是一样的,以为他们是同一个坐标系, +// 但这种情况的原因其实是因为两条线的起始点很接近,他们依然是两个独立的坐标系。 +// 左线和右线中间有渡线可以相互切换,但公里标如何转换暂时不清楚, +// 暂时考虑先通过添加转换配置来实现(比如转换可以配置0或根据道岔处勾股定理大概估算,虽然不同道岔处可能不一样,但暂时不考虑复杂情况)。 type KilometerMark struct { // 公里标坐标系 coordinate string - // 公里标上下行方向 - direction OperationDirection // 公里标值 value int64 } -func NewKilometerMark(coordinate string, direction OperationDirection, value int64) *KilometerMark { +func NewKilometerMark(coordinate string, value int64) *KilometerMark { return &KilometerMark{ coordinate: coordinate, - direction: direction, value: value, } } @@ -47,12 +40,23 @@ func (km *KilometerMark) IsCoordinateEqual(coordinate string) bool { // 公里标转换配置 type KilometerMarkConverter struct { - km1 KilometerMark - km2 KilometerMark + km1 *KilometerMark + km2 *KilometerMark // 趋势是否相同 trendSame bool } +func NewKilometerMarkConverter(km1, km2 *KilometerMark, trendSame bool) *KilometerMarkConverter { + if km1 == nil || km2 == nil { + panic("km1 or km2 is nil") + } + return &KilometerMarkConverter{ + km1: km1, + km2: km2, + trendSame: trendSame, + } +} + func (kmc *KilometerMarkConverter) Debug() string { return fmt.Sprintf("{%s<->%s(%s)}", kmc.km1.coordinate, kmc.km2.coordinate, strconv.FormatBool(kmc.trendSame)) } diff --git a/model/kilometer_mark_test.go b/model/kilometer_mark_test.go index 423a67f..02cc1ed 100644 --- a/model/kilometer_mark_test.go +++ b/model/kilometer_mark_test.go @@ -6,19 +6,11 @@ import ( func TestConvertKilometerMark(t *testing.T) { // 1. 配置公里标转换关系 - km1 := NewKilometerMark("DBCSK", OperationDirectionUp, 0) - km2 := NewKilometerMark("DCSK", OperationDirectionUp, 200) - kmc1 := &KilometerMarkConverter{ - km1: *km1, - km2: *km2, - trendSame: true, - } + km1 := NewKilometerMark("YDK", 0) + km2 := NewKilometerMark("ZDK", 200) + kmc1 := NewKilometerMarkConverter(km1, km2, true) t.Log(kmc1.Debug()) - kmc2 := &KilometerMarkConverter{ - km1: *km1, - km2: *km2, - trendSame: false, - } + kmc2 := *NewKilometerMarkConverter(km1, km2, false) t.Log(kmc2.Debug()) // 2. 验证公里标转换关系 @@ -28,12 +20,12 @@ func TestConvertKilometerMark(t *testing.T) { expect1 int64 expect2 int64 }{ - {km: NewKilometerMark("DBCSK", OperationDirectionUp, 0), coordinate: km2.Coordinate(), expect1: 200, expect2: 200}, - {km: NewKilometerMark("DBCSK", OperationDirectionUp, 100), coordinate: km2.Coordinate(), expect1: 300, expect2: 100}, - {km: NewKilometerMark("DBCSK", OperationDirectionUp, -100), coordinate: km2.Coordinate(), expect1: 100, expect2: 300}, - {km: NewKilometerMark("DCSK", OperationDirectionUp, 200), coordinate: km1.Coordinate(), expect1: 0, expect2: 0}, - {km: NewKilometerMark("DCSK", OperationDirectionUp, 300), coordinate: km1.Coordinate(), expect1: 100, expect2: -100}, - {km: NewKilometerMark("DCSK", OperationDirectionUp, 100), coordinate: km1.Coordinate(), expect1: -100, expect2: 100}, + {km: NewKilometerMark("YDK", 0), coordinate: km2.Coordinate(), expect1: 200, expect2: 200}, + {km: NewKilometerMark("YDK", 100), coordinate: km2.Coordinate(), expect1: 300, expect2: 100}, + {km: NewKilometerMark("YDK", -100), coordinate: km2.Coordinate(), expect1: 100, expect2: 300}, + {km: NewKilometerMark("ZDK", 200), coordinate: km1.Coordinate(), expect1: 0, expect2: 0}, + {km: NewKilometerMark("ZDK", 300), coordinate: km1.Coordinate(), expect1: 100, expect2: -100}, + {km: NewKilometerMark("ZDK", 100), coordinate: km1.Coordinate(), expect1: -100, expect2: 100}, } for _, test := range tests { result1 := kmc1.Convert(test.km, test.coordinate) diff --git a/model/link.go b/model/link.go index 4f19962..39c3bd3 100644 --- a/model/link.go +++ b/model/link.go @@ -3,27 +3,105 @@ package model import ( "fmt" "io" + "math" ) type RR = io.Writer // 轨道 +// 默认坐标系为以A端为起点,B端为终点的单轴坐标系 type Link interface { TwoPortsPipeElement IsEquals(pla *PipeLink, plb *PipeLink) bool + // 获取轨道长度,单位:毫米mm + GetLength() int64 } -// 轨道连接点 +// 轨道连接点(使用的是道岔岔心),然后有三个方向A/B/C,还是使用PipePort枚举,但表示的含义是方向,而不是道岔的三个端口 type LinkNode interface { Turnout() Turnout ThreePortsPipeElement } +type LinkDirection int + +const ( + LinkDirectionB2A LinkDirection = -1 + LinkDirectionA2B LinkDirection = 1 +) + +// link位置 +type LinkPosition struct { + link Link + pos int64 +} + +func NewLinkPosition(link Link, pos int64) *LinkPosition { + return &LinkPosition{ + link: link, + pos: pos, + } +} + +func (lp *LinkPosition) Link() Link { + return lp.link +} + +func (lp *LinkPosition) Position() int64 { + return lp.pos +} + +func (lp *LinkPosition) IsIn(linkRange LinkRange) bool { + return lp.link.Uid() == linkRange.link.Uid() && lp.IsInRange(linkRange.start, linkRange.end) +} + +func (lp *LinkPosition) IsInRange(a int64, b int64) bool { + start := int64(math.Min(float64(a), float64(b))) + end := int64(math.Max(float64(a), float64(b))) + return lp.pos >= start && lp.pos <= end +} + +// 移动位置 +func (lp *LinkPosition) Move(len uint32, direction LinkDirection) (overflow bool) { + lp.pos += int64(len) * int64(direction) + if lp.pos > lp.link.GetLength() || lp.pos < 0 { + overflow = true + } + return +} + +// link范围 +type LinkRange struct { + link Link + start int64 + end int64 +} + +// 构造link范围 +// link: 轨道 不能为nil +// a: 起始位置,b: 结束位置,单位:毫米mm,a和b不能相等 +func NewLinkRange(link Link, a int64, b int64) *LinkRange { + if link == nil { + panic("构造LinkRange错误: link不能为空") + } + if a == b { + panic("构造LinkRange错误: a和b不能相等") + } + start := int64(math.Min(float64(a), float64(b))) + end := int64(math.Max(float64(a), float64(b))) + return &LinkRange{ + link: link, + start: start, + end: end, + } +} + var _ Link = (*LinkImpl)(nil) var _ LinkNode = (*LinkNodeImpl)(nil) type LinkImpl struct { *TwoPortsPipeElementImpl + len int64 } type LinkNodeImpl struct { @@ -56,6 +134,14 @@ func (l *LinkImpl) IsEquals(pla *PipeLink, plb *PipeLink) bool { return l.uid == buildLinkUid(pla, plb) || l.uid == buildLinkUid(plb, pla) } +func (l *LinkImpl) GetLength() int64 { + return l.len +} + +func (l *LinkImpl) SetLength(len int64) { + l.len = len +} + func (n *LinkNodeImpl) Turnout() Turnout { return n.turnout } diff --git a/model/link_test.go b/model/link_test.go new file mode 100644 index 0000000..aaf461a --- /dev/null +++ b/model/link_test.go @@ -0,0 +1,68 @@ +package model_test + +import ( + "testing" + + "joylink.club/rtss-core/model" +) + +func TestLinkPositionInRange(t *testing.T) { + // 1. 配置连接关系 + turnout := model.NewTurnout("1") + ln1 := model.NewLinkNode(turnout) + pla := model.NewPipeLink(ln1, model.PipePortA) + link1 := model.NewLink(pla, nil) + link1.SetLength(1000) + linkPos1 := model.NewLinkPosition(link1, 100) + // 2. 验证连接关系 + inRangeTests := []struct { + start int64 + end int64 + expect bool + }{ + {start: 0, end: 50, expect: false}, + {start: 0, end: 100, expect: true}, + {start: 100, end: 101, expect: true}, + {start: 100, end: 100, expect: true}, + {start: 101, end: 100, expect: true}, + {start: 101, end: 200, expect: false}, + } + for _, test := range inRangeTests { + in := linkPos1.IsInRange(test.start, test.end) + if in != test.expect { + t.Errorf("expect: %v, but got: %v", test.expect, in) + } + } +} + +func TestLinkPositionAdd(t *testing.T) { + // 1. 配置连接关系 + turnout := model.NewTurnout("1") + ln1 := model.NewLinkNode(turnout) + pla := model.NewPipeLink(ln1, model.PipePortA) + link1 := model.NewLink(pla, nil) + link1.SetLength(1000) + // 2. 验证连接关系 + addTests := []struct { + len uint32 + direction model.LinkDirection + expect bool + expectValue int64 + }{ + {len: 0, direction: model.LinkDirectionA2B, expect: false, expectValue: 100}, + {len: 10, direction: model.LinkDirectionA2B, expect: false, expectValue: 110}, + {len: 10, direction: model.LinkDirectionB2A, expect: false, expectValue: 90}, + {len: 1000, direction: model.LinkDirectionA2B, expect: true, expectValue: 1100}, + {len: 1000, direction: model.LinkDirectionB2A, expect: true, expectValue: -900}, + } + for _, test := range addTests { + linkPos1 := model.NewLinkPosition(link1, 100) + overflow := linkPos1.Move(test.len, test.direction) + if overflow != test.expect { + t.Errorf("expect: %v, but got: %v", test.expect, overflow) + } + if linkPos1.Position() != test.expectValue { + t.Errorf("expect: %v, but got: %v", test.expectValue, linkPos1.Position()) + } + } +} diff --git a/model/model.go b/model/model.go index b60798d..dd98b09 100644 --- a/model/model.go +++ b/model/model.go @@ -80,7 +80,7 @@ type ThreePortsPipeElement interface { func checkPipePortLink(pe PipeElement, port PipePort, pipeLink *PipeLink) error { if pipeLink == nil { - slog.Debug("检查通道端口连接关系", "通道端口", DebugPipeLink(pe, port), "连接关系", "未连接") + slog.Debug("检查通道端口连接关系", "通道端口", DebugPipeLink(pe, port), "连接关系", "无连接") return nil } slog.Debug("检查通道端口连接关系", "通道端口", DebugPipeLink(pe, port), "连接关系", pipeLink.Debug()) diff --git a/model/section.go b/model/section.go index f254262..e20ab96 100644 --- a/model/section.go +++ b/model/section.go @@ -1,28 +1,45 @@ package model +import ( + "fmt" + "log/slog" +) + // 区段 type Section interface { RtssModel } // 物理区段(非道岔区段) +// 用于和道岔连接构成轨道网络 +// 然后使用轨道网络构建link和linkNode形成新的以道岔岔心为节点的网络,用于路径计算等 type PhysicalSection interface { TwoPortsPipeElement // 获取A端和B端的公里标 GetPaKm() *KilometerMark GetPbKm() *KilometerMark + // 计算各个端口到岔心的距离 + CalculateDistance(calculateKmDistance func(km1, km2 *KilometerMark) (int64, error)) error + GetLength() int64 } -// 道岔区段 +// 道岔区段(道岔物理区段) +// 道岔处的由至少3个及以上的计轴或绝缘节所确定的区段 type TurnoutSection interface { Section // 获取关联的道岔列表 GetTurnouts() []Turnout + // 添加关联道岔 + AddTurnout(turnout Turnout) } // 逻辑区段 +// 是在物理区段基础上进一步细分的区段,为了相对更精细的列车追踪和进路触发等控制 +// 最终映射到link上,做相应的逻辑处理 type LogicalSection interface { Section + // 所属物理区段(可能是一般物理区段也可能是道岔区段) + BelongSection() Section // 获取A端和B端的公里标 GetPaKm() *KilometerMark GetPbKm() *KilometerMark @@ -32,6 +49,7 @@ type PhysicalSectionImpl struct { *TwoPortsPipeElementImpl PaKm *KilometerMark PbKm *KilometerMark + len int64 } var _ PhysicalSection = (*PhysicalSectionImpl)(nil) @@ -44,6 +62,20 @@ func NewPhysicalSection(uid string) *PhysicalSectionImpl { } } +func (s *PhysicalSectionImpl) GetLength() int64 { + return s.len +} + +func (s *PhysicalSectionImpl) CalculateDistance(calculateKmDistance func(km1, km2 *KilometerMark) (int64, error)) error { + if s.PaKm == nil || s.PbKm == nil { + return fmt.Errorf("区段公里标不能为空") + } + var err error + s.len, err = calculateKmDistance(s.PaKm, s.PbKm) + slog.Debug("计算物理区段公里标距离", "uid", s.Uid(), "length", s.len) + return err +} + func (s *PhysicalSectionImpl) GetPaKm() *KilometerMark { return s.PaKm } @@ -77,6 +109,11 @@ func NewTurnoutSection(uid string) *TurnoutSectionImpl { } func (t *TurnoutSectionImpl) AddTurnout(turnout Turnout) { + for _, exist := range t.Turnouts { + if exist.Uid() == turnout.Uid() { + return + } + } t.Turnouts = append(t.Turnouts, turnout) } diff --git a/model/turnout.go b/model/turnout.go index 2d997c7..9ab56ec 100644 --- a/model/turnout.go +++ b/model/turnout.go @@ -1,6 +1,8 @@ package model import ( + "fmt" + "log/slog" "strings" ) @@ -15,16 +17,27 @@ type Turnout interface { GetPbKm() *KilometerMark // 获取道岔C端公里标 GetPcKm() *KilometerMark + // 计算各个端口到岔心的距离 + CalculateDistances(calculateKmDistance func(km1, km2 *KilometerMark) (int64, error)) error + // 获取到指定端口的长度 + GetLength(port PipePort) int64 + // 获取A端到B端的距离 + GetPaPbLength() int64 + // 获取A端到C端的距离 + GetPaPcLength() int64 } var _ Turnout = (*TurnoutImpl)(nil) type TurnoutImpl struct { *ThreePortsPipeElementImpl - Km *KilometerMark - PaKm *KilometerMark - PbKm *KilometerMark - PcKm *KilometerMark + Km *KilometerMark + PaKm *KilometerMark + PbKm *KilometerMark + PcKm *KilometerMark + paLen int64 + pbLen int64 + pcLen int64 } func NewTurnout(uid string) *TurnoutImpl { @@ -38,6 +51,47 @@ func NewTurnout(uid string) *TurnoutImpl { } } +func (t *TurnoutImpl) GetLength(port PipePort) int64 { + switch port { + case PipePortA: + return t.paLen + case PipePortB: + return t.pbLen + case PipePortC: + return t.pcLen + } + panic(fmt.Sprintf("获取道岔指定端口长度异常:错误的端口参数'%s'", port)) +} + +func (t *TurnoutImpl) GetPaPbLength() int64 { + return t.pbLen + t.paLen +} + +func (t *TurnoutImpl) GetPaPcLength() int64 { + return t.pcLen + t.paLen +} + +func (t *TurnoutImpl) CalculateDistances(calculateKmDistance func(km1, km2 *KilometerMark) (int64, error)) error { + if t.Km == nil || t.PaKm == nil || t.PbKm == nil || t.PcKm == nil { + return fmt.Errorf("道岔公里标不能为空") + } + var err error + t.paLen, err = calculateKmDistance(t.Km, t.PaKm) + if err != nil { + return err + } + t.pbLen, err = calculateKmDistance(t.Km, t.PbKm) + if err != nil { + return err + } + t.pcLen, err = calculateKmDistance(t.Km, t.PcKm) + if err != nil { + return err + } + slog.Debug("计算道岔公里标距离", "uid", t.Uid(), "A端距离", t.paLen, "B端距离", t.pbLen, "C端距离", t.pcLen) + return nil +} + func (t *TurnoutImpl) GetKm() *KilometerMark { return t.Km } diff --git a/repo/repo.go b/repo/repo.go index 618f5f5..7a08e82 100644 --- a/repo/repo.go +++ b/repo/repo.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "log/slog" + "math" "strings" "joylink.club/rtss-core/model" @@ -23,6 +24,8 @@ type Repo interface { CheckLinkAndLinkNodePipeLink() error // 转换公里标 ConvertKilometerMark(km *model.KilometerMark, targetCoordinate string) (int64, error) + // 计算公里标间距离 + CalculateKmDistance(km1 *model.KilometerMark, km2 *model.KilometerMark) (int64, error) } var _ Repo = (*RepoImpl)(nil) @@ -36,7 +39,7 @@ type RepoImpl struct { TurnoutMap map[string]model.Turnout LinkNodeMap map[string]model.LinkNode LinkMap map[string]model.Link - KilometerMarkConverters []model.KilometerMarkConverter + KilometerMarkConverters []*model.KilometerMarkConverter } func NewRepo(id string) *RepoImpl { @@ -48,7 +51,7 @@ func NewRepo(id string) *RepoImpl { TurnoutMap: make(map[string]model.Turnout), LinkNodeMap: make(map[string]model.LinkNode), LinkMap: make(map[string]model.Link), - KilometerMarkConverters: make([]model.KilometerMarkConverter, 0), + KilometerMarkConverters: make([]*model.KilometerMarkConverter, 0), } } @@ -68,6 +71,18 @@ func (r *RepoImpl) ConvertKilometerMark(km *model.KilometerMark, targetCoordinat return 0, fmt.Errorf("未找到公里标转换配置: %s<->%s, 全部配置项为: %s", km.Coordinate(), targetCoordinate, strings.Join(existConfigs, ",")) } +func (r *RepoImpl) CalculateKmDistance(km1 *model.KilometerMark, km2 *model.KilometerMark) (int64, error) { + if km1.Coordinate() == km2.Coordinate() { + return int64(math.Abs(float64(km2.Value() - km1.Value()))), nil + } else { + km1Value, err := r.ConvertKilometerMark(km1, km2.Coordinate()) + if err != nil { + return 0, err + } + return int64(math.Abs(float64(km2.Value() - km1Value))), nil + } +} + func (r *RepoImpl) CheckSectionAndTurnoutPortKms() error { for _, section := range r.PhysicalSectionMap { slog.Debug("检查区段公里标", "uid", section.Uid(), "A端公里标", section.GetPaKm(), "B端公里标", section.GetPbKm()) @@ -77,6 +92,9 @@ func (r *RepoImpl) CheckSectionAndTurnoutPortKms() error { if section.GetPbKm() == nil { r.BuildErrorInfos = append(r.BuildErrorInfos, fmt.Errorf("区段[uid=%s]无B端公里标", section.Uid())) } + if section.GetPaKm() != nil && section.GetPbKm() != nil { + section.CalculateDistance(r.CalculateKmDistance) + } } for _, turnout := range r.TurnoutMap { slog.Debug("检查道岔公里标", "uid", turnout.Uid(), "公里标", turnout.GetKm(), "A端公里标", turnout.GetPaKm(), "B端公里标", turnout.GetPbKm(), "C端公里标", turnout.GetPcKm()) @@ -92,6 +110,9 @@ func (r *RepoImpl) CheckSectionAndTurnoutPortKms() error { if turnout.GetPcKm() == nil { r.BuildErrorInfos = append(r.BuildErrorInfos, fmt.Errorf("道岔[uid=%s]无C端公里标", turnout.Uid())) } + if turnout.GetKm() != nil && turnout.GetPaKm() != nil && turnout.GetPbKm() != nil && turnout.GetPcKm() != nil { + turnout.CalculateDistances(r.CalculateKmDistance) + } } if len(r.BuildErrorInfos) > 0 { return errors.Join(r.BuildErrorInfos...) @@ -107,6 +128,16 @@ func (r *RepoImpl) CheckSectionAndTurnoutPipeLink() error { r.BuildErrorInfos = append(r.BuildErrorInfos, err) } } + for _, tsModel := range r.TurnoutSectionMap { + turnouts := make([]string, 0) + for _, turnout := range tsModel.GetTurnouts() { + turnouts = append(turnouts, turnout.Uid()) + } + slog.Debug("检查道岔区段关联道岔", "uid", tsModel.Uid(), "关联道岔", fmt.Sprintf("[%s]", strings.Join(turnouts, ","))) + if len(tsModel.GetTurnouts()) == 0 { + r.BuildErrorInfos = append(r.BuildErrorInfos, fmt.Errorf("道岔区段[uid=%s]未关联道岔", tsModel.Uid())) + } + } for _, turnout := range r.TurnoutMap { err := turnout.CheckPipeLink() if err != nil { @@ -185,29 +216,51 @@ func walkOverTurnouts(turnout model.Turnout, repo1 *RepoImpl) { func walkFromTurnoutPortToNextAndBuildLink(turnout model.Turnout, port model.PipePort, repo1 *RepoImpl) model.Turnout { slog.Debug("walkFromTurnoutPortToNextAndBuildLink", "道岔id", turnout.Uid(), "端口", port) ple := turnout.GetLinkedElement(port) + pathSections := make([]model.PhysicalSection, 0) + var nextTurnout model.Turnout = nil + var nextPort model.PipePort for { - if ple == nil { - link, exist := NewLinkNodeAndLinkAndBuildLinkship(turnout, port, nil, model.PipePortA, repo1) - if !exist { - repo1.LinkMap[link.Uid()] = link - } - slog.Debug("构建Link已存在", "LinkUid", link.Uid()) - return nil + if ple == nil { // 到尽头 + nextPort = model.PipePortA + break } - if ple.Pipe.IsThreePorts() { - nextTurnout := ple.Pipe.(model.Turnout) - nextPort := ple.Port - link, exist := NewLinkNodeAndLinkAndBuildLinkship(turnout, port, nextTurnout, nextPort, repo1) - if exist { - slog.Debug("构建Link已存在", "LinkUid", link.Uid()) - return nil - } - repo1.LinkMap[link.Uid()] = link - return nextTurnout - } else { - ple = ple.Pipe.(model.PhysicalSection).OppositePipeLink(ple.Port) + if ple.Pipe.IsThreePorts() { // 到下一个道岔 + nextTurnout = ple.Pipe.(model.Turnout) + nextPort = ple.Port + break + } else { // 到下一个区段 + section := ple.Pipe.(model.PhysicalSection) + pathSections = append(pathSections, section) + ple = section.OppositePipeLink(ple.Port) } } + link, exist := NewLinkNodeAndLinkAndBuildLinkship(turnout, port, nextTurnout, nextPort, repo1) + if exist { + slog.Debug("构建Link已存在", "LinkUid", link.Uid()) + return nil + } + repo1.LinkMap[link.Uid()] = link // 添加到仓库 + // 构建link的长度和道岔以及区段的映射 + buildLinkLengthAndElementMappings(link.(*model.LinkImpl), pathSections, repo1) + return nextTurnout +} + +func buildLinkLengthAndElementMappings(link *model.LinkImpl, pathSections []model.PhysicalSection, repo1 *RepoImpl) { + len := int64(0) + pale := link.GetLinkedElement(model.PipePortA) + if pale == nil { + panic("构建Link长度错误: A端通道连接关系不能为空") + } + len += pale.Pipe.(model.LinkNode).Turnout().GetLength(pale.Port) + pble := link.GetLinkedElement(model.PipePortB) + if pble != nil { + len += pble.Pipe.(model.LinkNode).Turnout().GetLength(pble.Port) + } + for _, section := range pathSections { + len += section.GetLength() + } + link.SetLength(len) + slog.Debug("构建Link长度", "LinkUid", link.Uid(), "长度(m)", len/1000) } func (r *RepoImpl) getOrBuildLinkNode(turnout model.Turnout) model.LinkNode { diff --git a/repo/repo_test.go b/repo/repo_test.go new file mode 100644 index 0000000..2b4a353 --- /dev/null +++ b/repo/repo_test.go @@ -0,0 +1,46 @@ +package repo_test + +import ( + "testing" + + "joylink.club/rtss-core/model" + "joylink.club/rtss-core/repo" +) + +func TestRepositoryConvertKilometerMark(t *testing.T) { + // 1. 配置公里标转换关系 + km1 := model.NewKilometerMark("YDK", 0) + km2 := model.NewKilometerMark("ZDK", 200) + kmc1 := model.NewKilometerMarkConverter(km1, km2, true) + t.Log(kmc1.Debug()) + repo1 := repo.NewRepo("Test") + repo1.KilometerMarkConverters = append(repo1.KilometerMarkConverters, kmc1) + + // 2. 验证公里标转换关系 + tests := []struct { + km *model.KilometerMark + targetCoordinate string + expect int64 + expectError bool + }{ + {km: model.NewKilometerMark("YDK", 0), targetCoordinate: km2.Coordinate(), expect: 200, expectError: false}, + {km: model.NewKilometerMark("ZDK", 0), targetCoordinate: km1.Coordinate(), expect: -200, expectError: false}, + {km: model.NewKilometerMark("YDK", 100), targetCoordinate: "Other", expect: 0, expectError: true}, + } + t.Logf("tests: %v", tests) + for _, test := range tests { + result, err := repo1.ConvertKilometerMark(test.km, test.targetCoordinate) + if test.expectError { + if err == nil { + t.Errorf("expect error, but got nil") + } + } else { + if err != nil { + t.Errorf("expect %d, but got error: %v", test.expect, err) + } + if result != test.expect { + t.Errorf("expect: %d, but got: %d", test.expect, result) + } + } + } +}