Compare commits

..

2 Commits

Author SHA1 Message Date
1a610c01ba 调整公里标概念,添加注释说明
完善道岔区段与道岔关系构建检查
添加Link位置和link范围工具类
完善example道岔区段关系构建
2024-07-10 18:09:16 +08:00
97445763fd 公里标数据构造
区段、物理区段、道岔区段、逻辑区段等概念区分抽象,重构实现
2024-07-08 19:47:30 +08:00
12 changed files with 698 additions and 152 deletions

View File

@ -11,23 +11,59 @@ import (
) )
func main() { func main() {
// slog.SetLogLoggerLevel(slog.LevelDebug) slog.SetLogLoggerLevel(slog.LevelDebug)
slog.SetLogLoggerLevel(slog.LevelInfo) // slog.SetLogLoggerLevel(slog.LevelInfo)
repo1 := repository.NewRepository("test1") repo1 := repository.NewRepository("test1")
rtssGraphicStorage := data_proto.GetXian6STYG() rtssGraphicStorage := data_proto.GetXian6STYG()
dataMapping := repository.NewDataMapping("1", rtssGraphicStorage) dataMapping := repository.NewDataMapping("1", rtssGraphicStorage)
repo1.DataMapping["1"] = dataMapping repo1.DataMapping["1"] = dataMapping
// log.Println(rtssGraphicStorage.Stations) basicDataMappingAndModelBuild(dataMapping, repo1)
// log.Println(rtssGraphicStorage.Section)
// log.Println(rtssGraphicStorage.Turnouts) // 构建公里标转换器
for _, station := range rtssGraphicStorage.Stations { buildKilometerMarkConverters(dataMapping, repo1)
// 构建区段关系
buildSectionRelationships(dataMapping, repo1)
// 构建道岔关系
buildTurnoutRelationships(dataMapping, repo1)
// 检查区段、道岔通道连接关系
err := repo1.CheckSectionAndTurnoutPipeLink()
if err != nil {
slog.Error("区段道岔连接关系检查错误", "errMsg", err)
return
} else {
slog.Info("区段、道岔连接关系检查通过")
}
// 构建区段、道岔公里标
buildKilometerMark(dataMapping, repo1)
err = repo1.CheckSectionAndTurnoutPortKms()
if err != nil {
slog.Error("区段道岔公里标检查错误", "errMsg", err)
return
}
// 构建link/linknode
repo1.BuildLinks()
// 检查link/linknode
err = repo1.CheckLinkAndLinkNodePipeLink()
if err != nil {
slog.Error("link/linknode连接关系检查错误", "errMsg", err)
return
} else {
slog.Info("link/linknode连接关系检查通过")
}
}
func basicDataMappingAndModelBuild(dataMapping *repository.DataMapping, repo1 *repository.Repository) {
// 车站
for _, station := range dataMapping.Stations {
dataMapping.StationDataMap[station.Common.Id] = station dataMapping.StationDataMap[station.Common.Id] = station
uid := station.Code uid := station.Code
dataMapping.AddIdMapping(repository.NewIdMapping(station.Common.Id, uid)) dataMapping.AddIdMapping(repository.NewIdMapping(station.Common.Id, uid))
stationModel := model.NewStation(uid, station.ConcentrationStations) stationModel := model.NewStation(uid, station.ConcentrationStations)
repo1.StationMap[uid] = &modelimpl.Station{StationImpl: stationModel} repo1.StationMap[uid] = &modelimpl.Station{StationImpl: stationModel}
} }
for _, section := range rtssGraphicStorage.Section { // 物理区段
for _, section := range dataMapping.Section {
if section.CentralizedStations == nil || len(section.CentralizedStations) == 0 { if section.CentralizedStations == nil || len(section.CentralizedStations) == 0 {
continue continue
} }
@ -38,10 +74,16 @@ func main() {
dataMapping.SectionDataMap[section.Common.Id] = section dataMapping.SectionDataMap[section.Common.Id] = section
uid := getSectionUid(section, belongStation, dataMapping.GetLineInfo()) uid := getSectionUid(section, belongStation, dataMapping.GetLineInfo())
dataMapping.AddIdMapping(repository.NewIdMapping(section.Common.Id, uid)) dataMapping.AddIdMapping(repository.NewIdMapping(section.Common.Id, uid))
sectionModel := model.NewSection(uid) if section.SectionType == data_proto.Section_Physical {
repo1.SectionMap[uid] = &modelimpl.Section{SectionImpl: sectionModel} 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 { // 道岔
for _, turnout := range dataMapping.Turnouts {
if turnout.CentralizedStations == nil || len(turnout.CentralizedStations) == 0 { if turnout.CentralizedStations == nil || len(turnout.CentralizedStations) == 0 {
continue continue
} }
@ -55,67 +97,86 @@ func main() {
turnoutModel := model.NewTurnout(uid) turnoutModel := model.NewTurnout(uid)
repo1.TurnoutMap[uid] = &modelimpl.Turnout{TurnoutImpl: turnoutModel} 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) {
buildSectionRelationships(dataMapping, repo1) if dataMapping.KilometerConvertList == nil || len(dataMapping.KilometerConvertList) == 0 {
// 构建道岔关系 slog.Info("没有公里标转换数据")
buildTurnoutRelationships(dataMapping, repo1)
// 检查区段、道岔通道连接关系
err := repo1.CheckPipeLink()
if err != nil {
slog.Error("区段道岔连接关系检查错误", "errMsg", err)
return return
} else {
slog.Info("区段、道岔连接关系检查通过")
} }
// 构建区段、道岔公里标 for _, kmConverter := range dataMapping.KilometerConvertList {
buildKilometerMark(dataMapping, repo1) km1 := convertKs2Km(kmConverter.KmA)
// 构建link/linknode km2 := convertKs2Km(kmConverter.KmB)
repo1.BuildLinks() repo1.KilometerMarkConverters = append(repo1.KilometerMarkConverters,
// 检查link/linknode model.NewKilometerMarkConverter(km1, km2, kmConverter.SameTrend))
err = repo1.CheckPipeLink()
if err != nil {
slog.Error("link/linknode连接关系检查错误", "errMsg", err)
return
} else {
slog.Info("link/linknode连接关系检查通过")
} }
} }
func convertKs2Km(ks *data_proto.KilometerSystem) *model.KilometerMark {
return model.NewKilometerMark(ks.CoordinateSystem, ks.Kilometer)
}
// 构建区段、道岔公里标 // 构建区段、道岔公里标
func buildKilometerMark(dataMapping *repository.DataMapping, repo1 *repository.Repository) { 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 {
km := convertKs2Km(turnout.KilometerSystem[0])
turnoutModel := repo1.TurnoutMap[dataMapping.IdMappingMap[turnout.Common.Id].Uid]
turnoutModel.(*modelimpl.Turnout).Km = km
}
}
for _, checkpoint := range dataMapping.AxleCountings { for _, checkpoint := range dataMapping.AxleCountings {
if checkpoint.AxleCountingRef == nil || len(checkpoint.AxleCountingRef) == 0 { if checkpoint.AxleCountingRef == nil || len(checkpoint.AxleCountingRef) == 0 {
repo1.BuildErrorInfos = append(repo1.BuildErrorInfos, fmt.Errorf("构建区段、道岔公里标错误:检测点[id=%d]未关联任何区段、道岔", checkpoint.Common.Id)) repo1.BuildErrorInfos = append(repo1.BuildErrorInfos, fmt.Errorf("构建区段、道岔公里标数据错误:检测点[id=%d]未关联任何区段、道岔", checkpoint.Common.Id))
} }
for _, linkship := range checkpoint.AxleCountingRef { for _, linkship := range checkpoint.AxleCountingRef {
slog.Info("区段检测点关联数据", "code", checkpoint.Code, "linkship", fmt.Sprintf("{type=%s, id=%d, port=%s}", linkship.DeviceType, linkship.Id, linkship.DevicePort)) slog.Debug("区段检测点关联数据", "code", checkpoint.Code, "linkship", fmt.Sprintf("{type=%s, id=%d, port=%s}", linkship.DeviceType, linkship.Id, linkship.DevicePort))
idmapping := dataMapping.IdMappingMap[linkship.Id] idmapping := dataMapping.IdMappingMap[linkship.Id]
if idmapping == nil { if idmapping == nil {
repo1.BuildErrorInfos = append(repo1.BuildErrorInfos, fmt.Errorf("构建区段、道岔公里标错误:检测点[id=%d]关联的{type=%s,id=%d}不存在", checkpoint.Common.Id, linkship.DeviceType, linkship.Id)) repo1.BuildErrorInfos = append(repo1.BuildErrorInfos, fmt.Errorf("构建区段、道岔公里标数据错误:检测点[id=%d]关联的{type=%s,id=%d}不存在", checkpoint.Common.Id, linkship.DeviceType, linkship.Id))
continue continue
} }
if linkship.DeviceType == data_proto.RelatedRef_Section { if linkship.DeviceType == data_proto.RelatedRef_Section {
sectionModel := repo1.SectionMap[idmapping.Uid] sectionModel, ok := repo1.PhysicalSectionMap[idmapping.Uid]
if sectionModel == nil { if !ok {
panic(fmt.Errorf("构建区段、道岔公里标错误:检测点[id=%d]关联的区段模型[uid=%s]不存在", checkpoint.Common.Id, idmapping.Uid)) panic(fmt.Errorf("构建区段、道岔公里标数据错误:检测点[id=%d]关联的区段模型[uid=%s]不存在", checkpoint.Common.Id, idmapping.Uid))
} }
sectionModel.(*modelimpl.Section).PaKm = 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 {
sectionModel.(*modelimpl.PhysicalSection).PbKm = km
} else {
panic(fmt.Errorf("构建区段、道岔公里标数据错误:检测点[id=%d]关联的区段模型[uid=%s]未知的端口类型: %v", checkpoint.Common.Id, idmapping.Uid, linkship.DevicePort))
}
} else if linkship.DeviceType == data_proto.RelatedRef_Turnout {
turnoutModel, ok := repo1.TurnoutMap[idmapping.Uid]
if !ok {
panic(fmt.Errorf("构建区段、道岔公里标数据错误:检测点[id=%d]关联的道岔模型[uid=%s]不存在", checkpoint.Common.Id, idmapping.Uid))
}
km := convertKs2Km(checkpoint.KilometerSystem)
if linkship.DevicePort == data_proto.RelatedRef_A {
turnoutModel.(*modelimpl.Turnout).PaKm = km
} else if linkship.DevicePort == data_proto.RelatedRef_B {
turnoutModel.(*modelimpl.Turnout).PbKm = km
} else if linkship.DevicePort == data_proto.RelatedRef_C {
turnoutModel.(*modelimpl.Turnout).PcKm = km
} else {
panic(fmt.Errorf("构建区段、道岔公里标数据错误:检测点[id=%d]关联的道岔模型[uid=%s]未知的端口类型: %v", checkpoint.Common.Id, idmapping.Uid, linkship.DevicePort))
}
} else {
panic(fmt.Errorf("构建区段、道岔公里标数据错误:检测点[id=%d]未知的关联设备类型: %v", checkpoint.Common.Id, linkship.DeviceType))
} }
} }
} }
} }
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) { func buildTurnoutRelationships(dataMapping *repository.DataMapping, repo1 *repository.Repository) {
for _, turnout := range dataMapping.TurnoutDataMap { for _, turnout := range dataMapping.TurnoutDataMap {
idmapping := dataMapping.IdMappingMap[turnout.Common.Id] idmapping := dataMapping.IdMappingMap[turnout.Common.Id]
@ -131,13 +192,41 @@ func buildTurnoutRelationships(dataMapping *repository.DataMapping, repo1 *repos
func buildSectionRelationships(dataMapping *repository.DataMapping, repo1 *repository.Repository) { func buildSectionRelationships(dataMapping *repository.DataMapping, repo1 *repository.Repository) {
for _, section := range dataMapping.SectionDataMap { for _, section := range dataMapping.SectionDataMap {
idmapping := dataMapping.IdMappingMap[section.Common.Id] idmapping := dataMapping.GetIdMapping(section.Common.Id)
if idmapping == nil { if section.SectionType == data_proto.Section_Physical {
panic(fmt.Errorf("构建区段关系错误idmapping异常为空")) 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.SectionMap[idmapping.Uid]
buildSectionPortLinkRelation(section.PaRef, sectionModel, model.PipePortA, repo1, dataMapping)
buildSectionPortLinkRelation(section.PbRef, sectionModel, model.PipePortB, repo1, dataMapping)
} }
} }
@ -148,7 +237,7 @@ func buildTurnoutPortLinkship(pref *data_proto.RelatedRef, sourceModel model.Tur
if pref.DeviceType == data_proto.RelatedRef_Section { if pref.DeviceType == data_proto.RelatedRef_Section {
idmapping2 := dataMapping.IdMappingMap[pref.Id] idmapping2 := dataMapping.IdMappingMap[pref.Id]
if idmapping2 != nil { if idmapping2 != nil {
sectionModel := repo1.SectionMap[idmapping2.Uid] sectionModel := repo1.PhysicalSectionMap[idmapping2.Uid]
if sectionModel != nil { if sectionModel != nil {
sourceModel.SetLinkedElement(port, model.NewPipeLink(sectionModel, convertPort(pref.DevicePort))) sourceModel.SetLinkedElement(port, model.NewPipeLink(sectionModel, convertPort(pref.DevicePort)))
} }
@ -165,14 +254,14 @@ func buildTurnoutPortLinkship(pref *data_proto.RelatedRef, sourceModel model.Tur
} }
} }
func buildSectionPortLinkRelation(pref *data_proto.RelatedRef, sourceModel model.Section, port model.PipePort, repo1 *repository.Repository, dataMapping *repository.DataMapping) { func buildSectionPortLinkRelation(pref *data_proto.RelatedRef, sourceModel model.PhysicalSection, port model.PipePort, repo1 *repository.Repository, dataMapping *repository.DataMapping) {
if pref == nil { if pref == nil {
return return
} }
if pref.DeviceType == data_proto.RelatedRef_Section { if pref.DeviceType == data_proto.RelatedRef_Section {
idmapping2 := dataMapping.IdMappingMap[pref.Id] idmapping2 := dataMapping.IdMappingMap[pref.Id]
if idmapping2 != nil { if idmapping2 != nil {
sectionModel := repo1.SectionMap[idmapping2.Uid] sectionModel := repo1.PhysicalSectionMap[idmapping2.Uid]
if sectionModel != nil { if sectionModel != nil {
sourceModel.SetLinkedElement(port, model.NewPipeLink(sectionModel, convertPort(pref.DevicePort))) sourceModel.SetLinkedElement(port, model.NewPipeLink(sectionModel, convertPort(pref.DevicePort)))
} }

View File

@ -2,6 +2,6 @@ package modelimpl
import "joylink.club/rtss-core/model" import "joylink.club/rtss-core/model"
type Section struct { type PhysicalSection struct {
*model.SectionImpl *model.PhysicalSectionImpl
} }

View File

@ -23,11 +23,20 @@ func NewIdMapping(graphicId uint32, uid string) *IdMapping {
type DataMapping struct { type DataMapping struct {
id string id string
*data_proto.RtssGraphicStorage *data_proto.RtssGraphicStorage
StationDataMap map[uint32]*data_proto.Station StationDataMap map[uint32]*data_proto.Station
SectionDataMap map[uint32]*data_proto.Section SectionDataMap map[uint32]*data_proto.Section
TurnoutDataMap map[uint32]*data_proto.Turnout TurnoutDataMap map[uint32]*data_proto.Turnout
IdMappingMap map[uint32]*IdMapping SectionCheckPointMap map[uint32]*data_proto.AxleCounting
UidMappingMap map[string]*IdMapping 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) { func (d *DataMapping) AddIdMapping(idMapping *IdMapping) {
@ -42,13 +51,14 @@ func (d *DataMapping) GetLineInfo() string {
func NewDataMapping(id string, storage *data_proto.RtssGraphicStorage) *DataMapping { func NewDataMapping(id string, storage *data_proto.RtssGraphicStorage) *DataMapping {
return &DataMapping{ return &DataMapping{
id: id, id: id,
RtssGraphicStorage: storage, RtssGraphicStorage: storage,
StationDataMap: make(map[uint32]*data_proto.Station), StationDataMap: make(map[uint32]*data_proto.Station),
SectionDataMap: make(map[uint32]*data_proto.Section), SectionDataMap: make(map[uint32]*data_proto.Section),
TurnoutDataMap: make(map[uint32]*data_proto.Turnout), TurnoutDataMap: make(map[uint32]*data_proto.Turnout),
IdMappingMap: make(map[uint32]*IdMapping), SectionCheckPointMap: make(map[uint32]*data_proto.AxleCounting),
UidMappingMap: make(map[string]*IdMapping), IdMappingMap: make(map[uint32]*IdMapping),
UidMappingMap: make(map[string]*IdMapping),
} }
} }

View File

@ -5,29 +5,22 @@ import (
"strconv" "strconv"
) )
// 运营方向(上行/下行) // 公里标
type OperationDirection int8 // 类似地铁线路一般正线都是双线公里标一般以类似YDK/ZDK开头
// 虽然从信号布置图上看YDK/ZDK相近位置的值似乎是一样的以为他们是同一个坐标系
const ( // 但这种情况的原因其实是因为两条线的起始点很接近,他们依然是两个独立的坐标系。
// 上行 // 左线和右线中间有渡线可以相互切换,但公里标如何转换暂时不清楚,
OperationDirectionUp OperationDirection = 1 // 暂时考虑先通过添加转换配置来实现比如转换可以配置0或根据道岔处勾股定理大概估算虽然不同道岔处可能不一样但暂时不考虑复杂情况
// 下行
OperationDirectionDown OperationDirection = 0
)
type KilometerMark struct { type KilometerMark struct {
// 公里标坐标系 // 公里标坐标系
coordinate string coordinate string
// 公里标上下行方向
direction OperationDirection
// 公里标值 // 公里标值
value int64 value int64
} }
func NewKilometerMark(coordinate string, direction OperationDirection, value int64) *KilometerMark { func NewKilometerMark(coordinate string, value int64) *KilometerMark {
return &KilometerMark{ return &KilometerMark{
coordinate: coordinate, coordinate: coordinate,
direction: direction,
value: value, value: value,
} }
} }
@ -36,6 +29,10 @@ func (km *KilometerMark) Coordinate() string {
return km.coordinate return km.coordinate
} }
func (km *KilometerMark) Value() int64 {
return km.value
}
// 是否为同一坐标系 // 是否为同一坐标系
func (km *KilometerMark) IsCoordinateEqual(coordinate string) bool { func (km *KilometerMark) IsCoordinateEqual(coordinate string) bool {
return km.coordinate == coordinate return km.coordinate == coordinate
@ -43,12 +40,23 @@ func (km *KilometerMark) IsCoordinateEqual(coordinate string) bool {
// 公里标转换配置 // 公里标转换配置
type KilometerMarkConverter struct { type KilometerMarkConverter struct {
km1 KilometerMark km1 *KilometerMark
km2 KilometerMark km2 *KilometerMark
// 趋势是否相同 // 趋势是否相同
trendSame bool 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 { func (kmc *KilometerMarkConverter) Debug() string {
return fmt.Sprintf("{%s<->%s(%s)}", kmc.km1.coordinate, kmc.km2.coordinate, strconv.FormatBool(kmc.trendSame)) return fmt.Sprintf("{%s<->%s(%s)}", kmc.km1.coordinate, kmc.km2.coordinate, strconv.FormatBool(kmc.trendSame))
} }

View File

@ -6,19 +6,11 @@ import (
func TestConvertKilometerMark(t *testing.T) { func TestConvertKilometerMark(t *testing.T) {
// 1. 配置公里标转换关系 // 1. 配置公里标转换关系
km1 := NewKilometerMark("DBCSK", OperationDirectionUp, 0) km1 := NewKilometerMark("YDK", 0)
km2 := NewKilometerMark("DCSK", OperationDirectionUp, 200) km2 := NewKilometerMark("ZDK", 200)
kmc1 := &KilometerMarkConverter{ kmc1 := NewKilometerMarkConverter(km1, km2, true)
km1: *km1,
km2: *km2,
trendSame: true,
}
t.Log(kmc1.Debug()) t.Log(kmc1.Debug())
kmc2 := &KilometerMarkConverter{ kmc2 := *NewKilometerMarkConverter(km1, km2, false)
km1: *km1,
km2: *km2,
trendSame: false,
}
t.Log(kmc2.Debug()) t.Log(kmc2.Debug())
// 2. 验证公里标转换关系 // 2. 验证公里标转换关系
@ -28,12 +20,12 @@ func TestConvertKilometerMark(t *testing.T) {
expect1 int64 expect1 int64
expect2 int64 expect2 int64
}{ }{
{km: NewKilometerMark("DBCSK", OperationDirectionUp, 0), coordinate: km2.Coordinate(), expect1: 200, expect2: 200}, {km: NewKilometerMark("YDK", 0), coordinate: km2.Coordinate(), expect1: 200, expect2: 200},
{km: NewKilometerMark("DBCSK", OperationDirectionUp, 100), coordinate: km2.Coordinate(), expect1: 300, expect2: 100}, {km: NewKilometerMark("YDK", 100), coordinate: km2.Coordinate(), expect1: 300, expect2: 100},
{km: NewKilometerMark("DBCSK", OperationDirectionUp, -100), coordinate: km2.Coordinate(), expect1: 100, expect2: 300}, {km: NewKilometerMark("YDK", -100), coordinate: km2.Coordinate(), expect1: 100, expect2: 300},
{km: NewKilometerMark("DCSK", OperationDirectionUp, 200), coordinate: km1.Coordinate(), expect1: 0, expect2: 0}, {km: NewKilometerMark("ZDK", 200), coordinate: km1.Coordinate(), expect1: 0, expect2: 0},
{km: NewKilometerMark("DCSK", OperationDirectionUp, 300), coordinate: km1.Coordinate(), expect1: 100, expect2: -100}, {km: NewKilometerMark("ZDK", 300), coordinate: km1.Coordinate(), expect1: 100, expect2: -100},
{km: NewKilometerMark("DCSK", OperationDirectionUp, 100), coordinate: km1.Coordinate(), expect1: -100, expect2: 100}, {km: NewKilometerMark("ZDK", 100), coordinate: km1.Coordinate(), expect1: -100, expect2: 100},
} }
for _, test := range tests { for _, test := range tests {
result1 := kmc1.Convert(test.km, test.coordinate) result1 := kmc1.Convert(test.km, test.coordinate)

View File

@ -3,27 +3,105 @@ package model
import ( import (
"fmt" "fmt"
"io" "io"
"math"
) )
type RR = io.Writer type RR = io.Writer
// 轨道 // 轨道
// 默认坐标系为以A端为起点B端为终点的单轴坐标系
type Link interface { type Link interface {
TwoPortsPipeElement TwoPortsPipeElement
IsEquals(pla *PipeLink, plb *PipeLink) bool IsEquals(pla *PipeLink, plb *PipeLink) bool
// 获取轨道长度单位毫米mm
GetLength() int64
} }
// 轨道连接点 // 轨道连接点(使用的是道岔岔心)然后有三个方向A/B/C还是使用PipePort枚举但表示的含义是方向而不是道岔的三个端口
type LinkNode interface { type LinkNode interface {
Turnout() Turnout Turnout() Turnout
ThreePortsPipeElement 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: 结束位置单位毫米mma和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 _ Link = (*LinkImpl)(nil)
var _ LinkNode = (*LinkNodeImpl)(nil) var _ LinkNode = (*LinkNodeImpl)(nil)
type LinkImpl struct { type LinkImpl struct {
*TwoPortsPipeElementImpl *TwoPortsPipeElementImpl
len int64
} }
type LinkNodeImpl struct { 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) 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 { func (n *LinkNodeImpl) Turnout() Turnout {
return n.turnout return n.turnout
} }

68
model/link_test.go Normal file
View File

@ -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())
}
}
}

View File

@ -80,7 +80,7 @@ type ThreePortsPipeElement interface {
func checkPipePortLink(pe PipeElement, port PipePort, pipeLink *PipeLink) error { func checkPipePortLink(pe PipeElement, port PipePort, pipeLink *PipeLink) error {
if pipeLink == nil { if pipeLink == nil {
slog.Debug("检查通道端口连接关系", "通道端口", DebugPipeLink(pe, port), "连接关系", "连接") slog.Debug("检查通道端口连接关系", "通道端口", DebugPipeLink(pe, port), "连接关系", "连接")
return nil return nil
} }
slog.Debug("检查通道端口连接关系", "通道端口", DebugPipeLink(pe, port), "连接关系", pipeLink.Debug()) slog.Debug("检查通道端口连接关系", "通道端口", DebugPipeLink(pe, port), "连接关系", pipeLink.Debug())
@ -138,21 +138,24 @@ func (t *TwoPortsPipeElementImpl) OppositePipeLink(port PipePort) *PipeLink {
func (s *TwoPortsPipeElementImpl) SetLinkedElement(port PipePort, pipeLink *PipeLink) error { func (s *TwoPortsPipeElementImpl) SetLinkedElement(port PipePort, pipeLink *PipeLink) error {
if port == PipePortA { if port == PipePortA {
if s.paPipeLink != nil { if s.paPipeLink != nil {
return fmt.Errorf("区段uid=%s端口A已经设置关联元素: {uid=%s, port=%s}", s.uid, s.paPipeLink.Pipe.Uid(), s.paPipeLink.Port) return fmt.Errorf("区段{uid=%s}端口A已经设置关联元素: {uid=%s, port=%s}", s.uid, s.paPipeLink.Pipe.Uid(), s.paPipeLink.Port)
} }
s.paPipeLink = pipeLink s.paPipeLink = pipeLink
} else if port == PipePortB { } else if port == PipePortB {
if s.pbPipeLink != nil { if s.pbPipeLink != nil {
return fmt.Errorf("区段uid=%s端口B已经设置关联元素: {uid=%s, port=%s}", s.uid, s.pbPipeLink.Pipe.Uid(), s.pbPipeLink.Port) return fmt.Errorf("区段{uid=%s}端口B已经设置关联元素: {uid=%s, port=%s}", s.uid, s.pbPipeLink.Pipe.Uid(), s.pbPipeLink.Port)
} }
s.pbPipeLink = pipeLink s.pbPipeLink = pipeLink
} else { } else {
return fmt.Errorf("区段uid=%s设置关联元素端口错误,不支持C端口", s.uid) return fmt.Errorf("区段{uid=%s}设置关联元素端口错误,不支持C端口", s.uid)
} }
return nil return nil
} }
func (s *TwoPortsPipeElementImpl) CheckPipeLink() error { func (s *TwoPortsPipeElementImpl) CheckPipeLink() error {
if s.paPipeLink == nil && s.pbPipeLink == nil {
return fmt.Errorf("区段{uid=%s}两端都没有关联通道连接关系", s.uid)
}
err := checkPipePortLink(s, PipePortA, s.paPipeLink) err := checkPipePortLink(s, PipePortA, s.paPipeLink)
if err != nil { if err != nil {
return err return err
@ -201,17 +204,17 @@ func (t *ThreePortsPipeElementImpl) GetLinkedElement(port PipePort) *PipeLink {
func (t *ThreePortsPipeElementImpl) SetLinkedElement(port PipePort, pipeLink *PipeLink) error { func (t *ThreePortsPipeElementImpl) SetLinkedElement(port PipePort, pipeLink *PipeLink) error {
if port == PipePortA { if port == PipePortA {
if t.paPipeLink != nil { if t.paPipeLink != nil {
return fmt.Errorf("道岔uid=%s端口A已经设置关联元素: {uid=%s, port=%s}", t.uid, t.paPipeLink.Pipe.Uid(), t.paPipeLink.Port) return fmt.Errorf("道岔{uid=%s}端口A已经设置关联元素: {uid=%s, port=%s}", t.uid, t.paPipeLink.Pipe.Uid(), t.paPipeLink.Port)
} }
t.paPipeLink = pipeLink t.paPipeLink = pipeLink
} else if port == PipePortB { } else if port == PipePortB {
if t.pbPipeLink != nil { if t.pbPipeLink != nil {
return fmt.Errorf("道岔uid=%s端口B已经设置关联元素: {uid=%s, port=%s}", t.uid, t.pbPipeLink.Pipe.Uid(), t.pbPipeLink.Port) return fmt.Errorf("道岔{uid=%s}端口B已经设置关联元素: {uid=%s, port=%s}", t.uid, t.pbPipeLink.Pipe.Uid(), t.pbPipeLink.Port)
} }
t.pbPipeLink = pipeLink t.pbPipeLink = pipeLink
} else if port == PipePortC { } else if port == PipePortC {
if t.pcPipeLink != nil { if t.pcPipeLink != nil {
return fmt.Errorf("道岔uid=%s端口C已经设置关联元素: {uid=%s, port=%s}", t.uid, t.pcPipeLink.Pipe.Uid(), t.pcPipeLink.Port) return fmt.Errorf("道岔{uid=%s}端口C已经设置关联元素: {uid=%s, port=%s}", t.uid, t.pcPipeLink.Pipe.Uid(), t.pcPipeLink.Port)
} }
t.pcPipeLink = pipeLink t.pcPipeLink = pipeLink
} }

View File

@ -1,33 +1,122 @@
package model package model
import (
"fmt"
"log/slog"
)
// 区段 // 区段
type Section interface { type Section interface {
RtssModel
}
// 物理区段(非道岔区段)
// 用于和道岔连接构成轨道网络
// 然后使用轨道网络构建link和linkNode形成新的以道岔岔心为节点的网络用于路径计算等
type PhysicalSection interface {
TwoPortsPipeElement TwoPortsPipeElement
// 获取A端和B端的公里标 // 获取A端和B端的公里标
GetPaKm() *KilometerMark GetPaKm() *KilometerMark
GetPbKm() *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
} }
type SectionImpl struct { type PhysicalSectionImpl struct {
*TwoPortsPipeElementImpl *TwoPortsPipeElementImpl
PaKm *KilometerMark PaKm *KilometerMark
PbKm *KilometerMark PbKm *KilometerMark
len int64
} }
var _ Section = (*SectionImpl)(nil) var _ PhysicalSection = (*PhysicalSectionImpl)(nil)
func NewSection(uid string) *SectionImpl { func NewPhysicalSection(uid string) *PhysicalSectionImpl {
return &SectionImpl{ return &PhysicalSectionImpl{
TwoPortsPipeElementImpl: &TwoPortsPipeElementImpl{ TwoPortsPipeElementImpl: &TwoPortsPipeElementImpl{
uid: uid, uid: uid,
}, },
} }
} }
func (s *SectionImpl) GetPaKm() *KilometerMark { 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 return s.PaKm
} }
func (s *SectionImpl) GetPbKm() *KilometerMark { func (s *PhysicalSectionImpl) GetPbKm() *KilometerMark {
return s.PbKm return s.PbKm
} }
type SectionImpl struct {
uid string
}
func (s *SectionImpl) Uid() string {
return s.uid
}
var _ TurnoutSection = (*TurnoutSectionImpl)(nil)
type TurnoutSectionImpl struct {
*SectionImpl
Turnouts []Turnout
}
func NewTurnoutSection(uid string) *TurnoutSectionImpl {
return &TurnoutSectionImpl{
SectionImpl: &SectionImpl{
uid: uid,
},
Turnouts: make([]Turnout, 0),
}
}
func (t *TurnoutSectionImpl) AddTurnout(turnout Turnout) {
for _, exist := range t.Turnouts {
if exist.Uid() == turnout.Uid() {
return
}
}
t.Turnouts = append(t.Turnouts, turnout)
}
func (t *TurnoutSectionImpl) GetTurnouts() []Turnout {
return t.Turnouts
}

View File

@ -1,6 +1,8 @@
package model package model
import ( import (
"fmt"
"log/slog"
"strings" "strings"
) )
@ -15,16 +17,27 @@ type Turnout interface {
GetPbKm() *KilometerMark GetPbKm() *KilometerMark
// 获取道岔C端公里标 // 获取道岔C端公里标
GetPcKm() *KilometerMark 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) var _ Turnout = (*TurnoutImpl)(nil)
type TurnoutImpl struct { type TurnoutImpl struct {
*ThreePortsPipeElementImpl *ThreePortsPipeElementImpl
Km *KilometerMark Km *KilometerMark
PaKm *KilometerMark PaKm *KilometerMark
PbKm *KilometerMark PbKm *KilometerMark
PcKm *KilometerMark PcKm *KilometerMark
paLen int64
pbLen int64
pcLen int64
} }
func NewTurnout(uid string) *TurnoutImpl { 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 { func (t *TurnoutImpl) GetKm() *KilometerMark {
return t.Km return t.Km
} }

View File

@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
"math"
"strings" "strings"
"joylink.club/rtss-core/model" "joylink.club/rtss-core/model"
@ -15,10 +16,16 @@ type Repo interface {
GetStationByUid(uid string) model.Station GetStationByUid(uid string) model.Station
GetSectionByUid(uid string) model.Section GetSectionByUid(uid string) model.Section
GetTurnoutByUid(uid string) model.Turnout GetTurnoutByUid(uid string) model.Turnout
// 检查通道连接关系 // 检查区段、道岔通道连接关系
CheckPipeLink() error CheckSectionAndTurnoutPipeLink() error
// 检查区段、道岔公里标
CheckSectionAndTurnoutPortKms() error
// 检查Link、LinkNode通道连接关系
CheckLinkAndLinkNodePipeLink() error
// 转换公里标 // 转换公里标
ConvertKilometerMark(km *model.KilometerMark, targetCoordinate string) (int64, error) ConvertKilometerMark(km *model.KilometerMark, targetCoordinate string) (int64, error)
// 计算公里标间距离
CalculateKmDistance(km1 *model.KilometerMark, km2 *model.KilometerMark) (int64, error)
} }
var _ Repo = (*RepoImpl)(nil) var _ Repo = (*RepoImpl)(nil)
@ -27,26 +34,31 @@ type RepoImpl struct {
id string id string
BuildErrorInfos []error BuildErrorInfos []error
StationMap map[string]model.Station StationMap map[string]model.Station
SectionMap map[string]model.Section PhysicalSectionMap map[string]model.PhysicalSection
TurnoutSectionMap map[string]model.TurnoutSection
TurnoutMap map[string]model.Turnout TurnoutMap map[string]model.Turnout
LinkNodeMap map[string]model.LinkNode LinkNodeMap map[string]model.LinkNode
LinkMap map[string]model.Link LinkMap map[string]model.Link
KilometerMarkConverters []model.KilometerMarkConverter KilometerMarkConverters []*model.KilometerMarkConverter
} }
func NewRepo(id string) *RepoImpl { func NewRepo(id string) *RepoImpl {
return &RepoImpl{ return &RepoImpl{
id: id, id: id,
StationMap: make(map[string]model.Station), StationMap: make(map[string]model.Station),
SectionMap: make(map[string]model.Section), PhysicalSectionMap: make(map[string]model.PhysicalSection),
TurnoutSectionMap: make(map[string]model.TurnoutSection),
TurnoutMap: make(map[string]model.Turnout), TurnoutMap: make(map[string]model.Turnout),
LinkNodeMap: make(map[string]model.LinkNode), LinkNodeMap: make(map[string]model.LinkNode),
LinkMap: make(map[string]model.Link), LinkMap: make(map[string]model.Link),
KilometerMarkConverters: make([]model.KilometerMarkConverter, 0), KilometerMarkConverters: make([]*model.KilometerMarkConverter, 0),
} }
} }
func (r *RepoImpl) ConvertKilometerMark(km *model.KilometerMark, targetCoordinate string) (int64, error) { func (r *RepoImpl) ConvertKilometerMark(km *model.KilometerMark, targetCoordinate string) (int64, error) {
if km.Coordinate() == targetCoordinate {
return km.Value(), nil
}
for _, converter := range r.KilometerMarkConverters { for _, converter := range r.KilometerMarkConverters {
if converter.IsMatch(km, targetCoordinate) { if converter.IsMatch(km, targetCoordinate) {
return converter.Convert(km, targetCoordinate), nil return converter.Convert(km, targetCoordinate), nil
@ -59,20 +71,87 @@ func (r *RepoImpl) ConvertKilometerMark(km *model.KilometerMark, targetCoordinat
return 0, fmt.Errorf("未找到公里标转换配置: %s<->%s, 全部配置项为: %s", km.Coordinate(), targetCoordinate, strings.Join(existConfigs, ",")) return 0, fmt.Errorf("未找到公里标转换配置: %s<->%s, 全部配置项为: %s", km.Coordinate(), targetCoordinate, strings.Join(existConfigs, ","))
} }
// CheckPipeLink implements Repo. func (r *RepoImpl) CalculateKmDistance(km1 *model.KilometerMark, km2 *model.KilometerMark) (int64, error) {
func (r *RepoImpl) CheckPipeLink() error { if km1.Coordinate() == km2.Coordinate() {
for _, section := range r.SectionMap { 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())
if section.GetPaKm() == nil {
r.BuildErrorInfos = append(r.BuildErrorInfos, fmt.Errorf("区段[uid=%s]无A端公里标", section.Uid()))
}
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())
if turnout.GetKm() == nil {
r.BuildErrorInfos = append(r.BuildErrorInfos, fmt.Errorf("道岔[uid=%s]无公里标", turnout.Uid()))
}
if turnout.GetPaKm() == nil {
r.BuildErrorInfos = append(r.BuildErrorInfos, fmt.Errorf("道岔[uid=%s]无A端公里标", turnout.Uid()))
}
if turnout.GetPbKm() == nil {
r.BuildErrorInfos = append(r.BuildErrorInfos, fmt.Errorf("道岔[uid=%s]无B端公里标", turnout.Uid()))
}
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...)
}
return nil
}
// CheckSectionAndTurnoutPipeLink implements Repo.
func (r *RepoImpl) CheckSectionAndTurnoutPipeLink() error {
for _, section := range r.PhysicalSectionMap {
err := section.CheckPipeLink() err := section.CheckPipeLink()
if err != nil { if err != nil {
r.BuildErrorInfos = append(r.BuildErrorInfos, err) 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 { for _, turnout := range r.TurnoutMap {
err := turnout.CheckPipeLink() err := turnout.CheckPipeLink()
if err != nil { if err != nil {
r.BuildErrorInfos = append(r.BuildErrorInfos, err) r.BuildErrorInfos = append(r.BuildErrorInfos, err)
} }
} }
if len(r.BuildErrorInfos) > 0 {
return errors.Join(r.BuildErrorInfos...)
}
return nil
}
// CheckLinkAndLinkNodePipeLink implements Repo.
func (r *RepoImpl) CheckLinkAndLinkNodePipeLink() error {
slog.Debug("检查通道连接关系", "link数量", len(r.LinkMap)) slog.Debug("检查通道连接关系", "link数量", len(r.LinkMap))
for _, link := range r.LinkMap { for _, link := range r.LinkMap {
err := link.CheckPipeLink() err := link.CheckPipeLink()
@ -94,7 +173,7 @@ func (r *RepoImpl) CheckPipeLink() error {
// GetSectionByUid implements Repo. // GetSectionByUid implements Repo.
func (r *RepoImpl) GetSectionByUid(uid string) model.Section { func (r *RepoImpl) GetSectionByUid(uid string) model.Section {
return r.SectionMap[uid] return r.PhysicalSectionMap[uid]
} }
// GetStationByUid implements Repo. // GetStationByUid implements Repo.
@ -137,29 +216,51 @@ func walkOverTurnouts(turnout model.Turnout, repo1 *RepoImpl) {
func walkFromTurnoutPortToNextAndBuildLink(turnout model.Turnout, port model.PipePort, repo1 *RepoImpl) model.Turnout { func walkFromTurnoutPortToNextAndBuildLink(turnout model.Turnout, port model.PipePort, repo1 *RepoImpl) model.Turnout {
slog.Debug("walkFromTurnoutPortToNextAndBuildLink", "道岔id", turnout.Uid(), "端口", port) slog.Debug("walkFromTurnoutPortToNextAndBuildLink", "道岔id", turnout.Uid(), "端口", port)
ple := turnout.GetLinkedElement(port) ple := turnout.GetLinkedElement(port)
pathSections := make([]model.PhysicalSection, 0)
var nextTurnout model.Turnout = nil
var nextPort model.PipePort
for { for {
if ple == nil { if ple == nil { // 到尽头
link, exist := NewLinkNodeAndLinkAndBuildLinkship(turnout, port, nil, model.PipePortA, repo1) nextPort = model.PipePortA
if !exist { break
repo1.LinkMap[link.Uid()] = link
}
slog.Debug("构建Link已存在", "LinkUid", link.Uid())
return nil
} }
if ple.Pipe.IsThreePorts() { if ple.Pipe.IsThreePorts() { // 到下一个道岔
nextTurnout := ple.Pipe.(model.Turnout) nextTurnout = ple.Pipe.(model.Turnout)
nextPort := ple.Port nextPort = ple.Port
link, exist := NewLinkNodeAndLinkAndBuildLinkship(turnout, port, nextTurnout, nextPort, repo1) break
if exist { } else { // 到下一个区段
slog.Debug("构建Link已存在", "LinkUid", link.Uid()) section := ple.Pipe.(model.PhysicalSection)
return nil pathSections = append(pathSections, section)
} ple = section.OppositePipeLink(ple.Port)
repo1.LinkMap[link.Uid()] = link
return nextTurnout
} else {
ple = ple.Pipe.(model.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 { func (r *RepoImpl) getOrBuildLinkNode(turnout model.Turnout) model.LinkNode {

46
repo/repo_test.go Normal file
View File

@ -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)
}
}
}
}