调整公里标概念,添加注释说明

完善道岔区段与道岔关系构建检查
添加Link位置和link范围工具类
完善example道岔区段关系构建
This commit is contained in:
soul-walker 2024-07-10 18:09:16 +08:00
parent 97445763fd
commit 1a610c01ba
11 changed files with 532 additions and 137 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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: 结束位置单位毫米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 _ 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
}

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 {
if pipeLink == nil {
slog.Debug("检查通道端口连接关系", "通道端口", DebugPipeLink(pe, port), "连接关系", "连接")
slog.Debug("检查通道端口连接关系", "通道端口", DebugPipeLink(pe, port), "连接关系", "连接")
return nil
}
slog.Debug("检查通道端口连接关系", "通道端口", DebugPipeLink(pe, port), "连接关系", pipeLink.Debug())

View File

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

View File

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

View File

@ -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 {

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