rts-sim-module/repository/repository_manager.go

664 lines
19 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package repository
import (
"errors"
"fmt"
"joylink.club/rtsssimulation/repository/model/proto"
"joylink.club/rtsssimulation/util/number"
"math"
"strconv"
)
var repositoryMap = make(map[string]*Repository)
func BuildRepository(source *proto.Repository) (*Repository, error) {
repository := newRepository(source.Id, source.Version)
buildModels(source, repository)
err := buildModelRelationship(source, repository)
if err != nil {
return nil, err
}
err = buildLinksAndRelate(repository)
if err != nil {
return nil, err
}
repositoryMap[buildRepositoryKey(source.Id, source.Version)] = repository
return repository, err
}
func FindRepository(id string, version string) *Repository {
return repositoryMap[buildRepositoryKey(id, version)]
}
func buildRepositoryKey(id string, version string) string {
return id + "_" + version
}
func buildModels(source *proto.Repository, repository *Repository) {
for _, protoData := range source.KilometerConverts {
repository.addKilometerConvert(protoData)
}
for _, protoData := range source.PhysicalSections {
m := NewPhysicalSection(protoData.Id)
repository.physicalSectionMap[m.Id()] = m
}
for _, protoData := range source.CheckPoints {
m := NewCheckPoint(protoData.Id, protoData.Km, protoData.Type)
repository.checkPointMap[m.Id()] = m
}
for _, protoData := range source.Turnouts {
m := NewTurnout(protoData.Id, protoData.Km)
repository.turnoutMap[m.Id()] = m
}
for _, protoData := range source.Signals {
m := NewSignal(protoData.Id, protoData.Km)
repository.signalMap[m.Id()] = m
}
for _, protoData := range source.Transponders {
m := NewTransponder(protoData.Id, protoData.Km)
repository.responderMap[m.Id()] = m
}
for _, protoData := range source.Slopes {
m := NewSlope(protoData.Id, protoData.Kms, protoData.Degree)
repository.slopeMap[m.Id()] = m
}
for _, protoData := range source.SectionalCurvatures {
m := NewSectionalCurvature(protoData.Id, protoData.Kms, protoData.Radius)
repository.sectionalCurvatureMap[m.Id()] = m
}
}
func buildModelRelationship(source *proto.Repository, repository *Repository) error {
err := buildCheckPointRelationShip(source, repository)
if err != nil {
return err
}
err = buildPhysicalSectionRelationShip(source, repository)
if err != nil {
return err
}
err = buildTurnoutRelationShip(source, repository)
if err != nil {
return err
}
err = buildSignalRelationShip(source, repository)
if err != nil {
return err
}
err = buildResponderRelationShip(source, repository)
if err != nil {
return err
}
return err
}
func buildCheckPointRelationShip(source *proto.Repository, repo *Repository) error {
for _, protoData := range source.CheckPoints {
cp := repo.checkPointMap[protoData.Id]
isBoundary := protoData.Type == proto.CheckPointType_Boundary
for _, protoDp := range protoData.DevicePorts {
switch protoDp.DeviceType {
case proto.DeviceType_DeviceType_PhysicalSection:
section := repo.physicalSectionMap[protoDp.DeviceId]
if isBoundary { //是区段边界
err := section.bindBoundaryKm(cp.km, protoDp.Port)
if err != nil {
return err
}
} else {
//检测点关联区段端口
cp.bindDevicePort(&PhysicalSectionPort{
section: section,
port: protoDp.Port,
})
//区段关联检测点
section.bindDevices(cp)
//如果区段边界未设置
if section.findBoundaryKmByPort(protoDp.Port) == nil {
if err := section.bindBoundaryKm(cp.km, protoDp.Port); err != nil {
return err
}
}
}
case proto.DeviceType_DeviceType_Turnout:
turnout := repo.turnoutMap[protoDp.DeviceId]
if isBoundary { //是区段边界
err := turnout.bindBoundaryKm(cp.km, protoDp.Port)
if err != nil {
return err
}
} else {
//检测点关联道岔端口
cp.bindDevicePort(&TurnoutPort{
turnout: turnout,
port: protoDp.Port,
})
//道岔关联检测点
turnout.bindDevice(cp, protoDp.Port)
//如果区段边界未设置
if turnout.findBoundaryKmByPort(protoDp.Port) == nil {
if err := turnout.bindBoundaryKm(cp.km, protoDp.Port); err != nil {
return err
}
}
}
}
}
}
return nil
}
func buildResponderRelationShip(source *proto.Repository, repository *Repository) error {
for _, protoData := range source.Transponders {
responder := repository.responderMap[protoData.Id]
//应答器和区段相互关联
if protoData.SectionId != "" {
interrelateResponderAndPhysicalSection(responder, repository.physicalSectionMap[protoData.SectionId])
}
//应答器和道岔相互关联
tp := protoData.TurnoutPort
if tp != nil {
if tp.DeviceType != proto.DeviceType_DeviceType_Turnout {
return errors.New(fmt.Sprintf("应答器[%s]关联的[%s-%s-%s]并非道岔端口",
responder.Id(), tp.DeviceId, tp.DeviceType, tp.Port))
}
interrelateResponderAndTurnout(responder, repository.turnoutMap[tp.DeviceId], tp.Port)
}
}
return nil
}
func buildSignalRelationShip(source *proto.Repository, repository *Repository) error {
for _, protoData := range source.Signals {
signal := repository.signalMap[protoData.Id]
//信号机和区段相互关联
if protoData.SectionId != "" {
interrelateSignalAndPhysicalSection(signal, repository.physicalSectionMap[protoData.SectionId])
}
//信号机和道岔相互关联
tp := protoData.TurnoutPort
if tp != nil {
if tp.DeviceType != proto.DeviceType_DeviceType_Turnout {
return errors.New(fmt.Sprintf("信号机[%s]关联的[%s-%s-%s]并非道岔端口",
signal.Id(), tp.DeviceId, tp.DeviceType, tp.Port))
}
interrelateSignalAndTurnout(signal, repository.turnoutMap[tp.DeviceId], tp.Port)
}
}
return nil
}
func buildTurnoutRelationShip(source *proto.Repository, repo *Repository) error {
for _, protoData := range source.Turnouts {
turnout := repo.turnoutMap[protoData.Id]
err := buildTurnoutPortRelation(repo, turnout, proto.Port_A, protoData.ADevicePort)
if err != nil {
return err
}
err = buildTurnoutPortRelation(repo, turnout, proto.Port_B, protoData.BDevicePort)
if err != nil {
return err
}
err = buildTurnoutPortRelation(repo, turnout, proto.Port_C, protoData.CDevicePort)
if err != nil {
return err
}
}
return nil
}
func buildTurnoutPortRelation(repo *Repository, turnout *Turnout, port proto.Port, protoDp *proto.DevicePort) error {
model, err := repo.getModel(protoDp.DeviceId, protoDp.DeviceType)
if err != nil {
return err
}
dp, err := buildDevicePort(model, protoDp.Port)
if err != nil {
return err
}
err = turnout.bindDevicePort(port, dp)
return err
}
func buildPhysicalSectionRelationShip(source *proto.Repository, repository *Repository) error {
for _, protoData := range source.PhysicalSections {
section := repository.physicalSectionMap[protoData.Id]
//A端关联
if protoData.ADevicePort != nil {
err := buildSectionPortRelation(repository, section, proto.Port_A, protoData.ADevicePort)
if err != nil {
return err
}
}
//B端关联
if protoData.BDevicePort != nil {
err := buildSectionPortRelation(repository, section, proto.Port_B, protoData.BDevicePort)
if err != nil {
return err
}
}
//道岔关联
for _, turnoutId := range protoData.TurnoutIds {
turnout := repository.turnoutMap[turnoutId]
if turnout == nil {
return errors.New(fmt.Sprintf("id[%s]的道岔不存在", turnoutId))
}
section.bindTurnouts(turnout)
turnout.section = section
}
}
return nil
}
// 构建物理区段指定端口的关联关系。区段{section}的{port}端口关联{protoDp}
func buildSectionPortRelation(repo *Repository, section *PhysicalSection, port proto.Port, protoDp *proto.DevicePort) error {
model, err := repo.getModel(protoDp.DeviceId, protoDp.DeviceType)
if err != nil {
return err
}
dp, err := buildDevicePort(model, protoDp.Port)
if err != nil {
return err
}
err = section.bindDevicePort(port, dp)
if err != nil {
return err
}
return nil
}
func buildDevicePort(device Identity, port proto.Port) (DevicePort, error) {
var dp DevicePort
switch device.Type() {
case proto.DeviceType_DeviceType_PhysicalSection:
section := device.(*PhysicalSection)
dp = &PhysicalSectionPort{
section: section,
port: port,
}
case proto.DeviceType_DeviceType_Turnout:
turnout := device.(*Turnout)
dp = &TurnoutPort{
turnout: turnout,
port: port,
}
case proto.DeviceType_DeviceType_Link:
link := device.(*Link)
dp = &LinkPort{
link: link,
port: port,
}
default:
return nil, errors.New(fmt.Sprintf("[%s]类型设备没有端口", device.Type()))
}
return dp, nil
}
// 相互关联道岔和检测点
func interrelateTurnoutAndCheckPoints(turnout *Turnout, point *CheckPoint, port proto.Port) {
turnout.bindDevice(point, port)
point.bindDevicePort(&TurnoutPort{
turnout: turnout,
port: port,
})
}
// 相互关联物理区段和信号机
func interrelateSignalAndPhysicalSection(signal *Signal, section *PhysicalSection) {
section.bindDevices(signal)
//signal.bindSection(section)
}
// 相互关联信号机和道岔
func interrelateSignalAndTurnout(signal *Signal, turnout *Turnout, port proto.Port) {
//signal.bindTurnoutPort(TurnoutPort{
// turnout: turnout,
// port: port,
//})
turnout.bindDevice(signal, port)
}
// 相互关联应答器和物理区段
func interrelateResponderAndPhysicalSection(responder *Transponder, section *PhysicalSection) {
//responder.bindSection(section)
section.bindDevices(responder)
}
// 相互关联应答器和道岔
func interrelateResponderAndTurnout(responder *Transponder, turnout *Turnout, port proto.Port) {
//responder.bindTurnoutPort(TurnoutPort{
// turnout: turnout,
// port: port,
//})
turnout.bindDevice(responder, port)
}
func buildLinksAndRelate(repo *Repository) error {
//构建link
err := buildLinks(repo)
if err != nil {
return err
}
//Slope关联Link
err = slopeRelateLink(repo)
if err != nil {
return err
}
//SectionalCurvature关联Link
err = sectionalCurvatureRelateLink(repo)
if err != nil {
return err
}
return nil
}
func buildLinks(repo *Repository) error {
visitedTurnoutPortMap := make(map[string]bool)
allTurnouts := repo.TurnoutList()
linkIdGenerator := 10000
for {
//找一个未遍历过的道岔端口
startTp := getATurnoutPort(allTurnouts, visitedTurnoutPortMap)
if startTp == nil {
break
}
//以始端道岔的公里标作为公里标换算的基准
baseKm := startTp.turnout.km
//创建基础Link
link := &Link{
Identity: identity{
id: strconv.Itoa(linkIdGenerator), //由于发给动力学的link的id是数字所以这里也用数字
deviceType: proto.DeviceType_DeviceType_Link,
}}
linkIdGenerator++
//以此道岔端口作为Link的A端节点
interrelateLinkAndTurnout(repo, baseKm, startTp, &LinkPort{link, proto.Port_A})
//沿着道岔端口方向一直寻找到轨道尽头或者下一个道岔端口。构建并关联中间的设备在link上的位置
endTp, endKm, err := findEndTurnoutPortOrEndKm(repo, link, startTp, baseKm)
if err != nil {
return err
}
//以下一个道岔端口作为Link的B端节点
if endTp != nil {
visitedTurnoutPortMap[buildTurnoutPortKey(endTp)] = true
endKm = endTp.turnout.km
interrelateLinkAndTurnout(repo, baseKm, endTp, &LinkPort{link, proto.Port_B})
} else {
link.bindKm(endKm, proto.Port_B)
}
//计算Link长度
convertedKm, err := convertKilometer(repo, endKm, baseKm.CoordinateSystem, baseKm.Direction)
if err != nil {
return err
}
link.length = int64(math.Abs(float64(convertedKm.Value - baseKm.Value)))
repo.linkMap[link.Id()] = link
}
return nil
}
func findEndTurnoutPortOrEndKm(repo *Repository, link *Link, startTp *TurnoutPort,
baseKm *proto.Kilometer) (*TurnoutPort, *proto.Kilometer, error) {
var endTp *TurnoutPort
var endKm *proto.Kilometer
var err error
visitedModelMap := make(map[string]bool)
var currentDp DevicePort = startTp
devices := startTp.turnout.findDevicesByPort(startTp.port)
for {
//遍历设备并构建、关联其在Link上的位置
err = relateDevicesAndLink(repo, link, baseKm, visitedModelMap, devices...)
if err != nil {
return nil, nil, err
}
//顺着端口寻找下一个设备端口
var nextDp DevicePort
switch currentDp.Device().Type() {
case proto.DeviceType_DeviceType_PhysicalSection:
section := currentDp.Device().(*PhysicalSection)
relatePhysicalSectionAndLink(repo, section, link, baseKm)
nextDp = section.findOtherDevicePort(currentDp.Port())
if nextDp == nil {
endKm = section.findOtherBoundaryKmByPort(currentDp.Port())
}
case proto.DeviceType_DeviceType_Turnout:
turnout := currentDp.Device().(*Turnout)
nextDp = turnout.findDevicePortByPort(currentDp.Port())
if nextDp == nil {
endKm = turnout.findBoundaryKmByPort(currentDp.Port())
}
}
//根据下一个端口设备的信息决定是否结束循环
if nextDp == nil {
break
}
currentDp = nextDp
var nextIsTp bool
switch nextDp.Device().Type() {
case proto.DeviceType_DeviceType_PhysicalSection:
devices = nextDp.Device().(*PhysicalSection).devices
case proto.DeviceType_DeviceType_Turnout:
nextIsTp = true
endTp = nextDp.(*TurnoutPort)
devices = nextDp.Device().(*Turnout).findDevicesByPort(nextDp.Port())
err = relateDevicesAndLink(repo, link, baseKm, visitedModelMap, devices...)
if err != nil {
return nil, nil, err
}
}
if nextIsTp {
break
}
}
return endTp, endKm, err
}
func relatePhysicalSectionAndLink(repo *Repository, section *PhysicalSection, link *Link, baseKm *proto.Kilometer) {
link.bindPhysicalSections(section)
aKm, _ := convertKilometer(repo, section.aKm, baseKm.CoordinateSystem, baseKm.Direction)
bKm, _ := convertKilometer(repo, section.bKm, baseKm.CoordinateSystem, baseKm.Direction)
aOffset := number.Abs(aKm.Value - baseKm.Value)
bOffset := number.Abs(bKm.Value - baseKm.Value)
section.bindStartLinkPosition(&LinkPosition{
link: link,
offset: number.Min(aOffset, bOffset),
})
section.bindEndLinkPosition(&LinkPosition{
link: link,
offset: number.Max(aOffset, bOffset),
})
}
func getATurnoutPort(turnouts []*Turnout, visitedTurnoutMap map[string]bool) *TurnoutPort {
portSlice := []proto.Port{proto.Port_A, proto.Port_B, proto.Port_C}
for _, turnout := range turnouts {
for _, port := range portSlice {
tp := &TurnoutPort{
turnout: turnout,
port: port,
}
key := buildTurnoutPortKey(tp)
if !visitedTurnoutMap[key] {
visitedTurnoutMap[key] = true
return tp
}
}
}
return nil
}
func buildTurnoutPortKey(tp *TurnoutPort) string {
return fmt.Sprintf("%v-%s", tp.turnout, tp.port)
}
func relateDevicesAndLink(repo *Repository, link *Link, startKm *proto.Kilometer, visitedModelMap map[string]bool, devices ...Identity) error {
for _, device := range devices {
if visitedModelMap[device.Id()] {
continue
}
km := findModelKm(device)
if km == nil {
continue
}
convertedKm, err := convertKilometer(repo, km, startKm.CoordinateSystem, startKm.Direction)
if err != nil {
return err
}
offset := int64(math.Abs(float64(convertedKm.Value - startKm.Value)))
linkPosition := &LinkPosition{
link: link,
offset: offset,
}
switch device.Type() {
case proto.DeviceType_DeviceType_CheckPoint:
device.(*CheckPoint).bindLinkPosition(linkPosition)
case proto.DeviceType_DeviceType_Signal:
device.(*Signal).bindLinkPosition(linkPosition)
case proto.DeviceType_DeviceType_Transponder:
device.(*Transponder).bindLinkPosition(linkPosition)
}
}
return nil
}
func interrelateLinkAndTurnout(repo *Repository, baseKm *proto.Kilometer, tp *TurnoutPort, linkPort *LinkPort) {
tp.turnout.bindLinkPort(tp.port, linkPort)
linkPort.link.bindTurnoutPort(linkPort.port, tp)
tpKm := tp.turnout.findBoundaryKmByPort(tp.port)
tpKm, _ = convertKilometer(repo, tpKm, baseKm.CoordinateSystem, baseKm.Direction)
offset := number.Abs(tpKm.Value - baseKm.Value)
tp.turnout.bindLinkPosition(tp.port, &LinkPosition{
link: linkPort.link,
offset: offset,
})
}
func findModelKm(model Identity) *proto.Kilometer {
switch model.Type() {
case proto.DeviceType_DeviceType_Signal:
return model.(*Signal).km
case proto.DeviceType_DeviceType_Transponder:
return model.(*Transponder).km
case proto.DeviceType_DeviceType_CheckPoint:
return model.(*CheckPoint).km
case proto.DeviceType_DeviceType_Turnout:
return model.(*Turnout).km
}
return nil
}
func slopeRelateLink(repo *Repository) error {
slopeMap := repo.slopeMap
for _, slope := range slopeMap {
start, end, err := calculateLinkSegment(repo, slope.kms[0], slope.kms[1])
if err != nil {
return err
}
slope.bindStartLinkPosition(start)
slope.bindEndLinkPosition(end)
}
return nil
}
func calculateLinkSegment(repo *Repository, startKm *proto.Kilometer,
endKm *proto.Kilometer) (*LinkPosition, *LinkPosition, error) {
endKm, err := convertKilometer(repo, endKm, startKm.CoordinateSystem, startKm.Direction)
if err != nil {
return nil, nil, err
}
var start *LinkPosition
var end *LinkPosition
var minKmValue float64
var minKmLink *Link
var maxKmValue float64
var maxKmLink *Link
for _, link := range repo.linkMap {
//将link两端的公里标转为aKm同坐标系的公里标
linkAKm, err := convertKilometer(repo, link.aKm, startKm.CoordinateSystem, startKm.Direction)
if err != nil || linkAKm.Direction != startKm.Direction {
continue
}
linkBKm, err := convertKilometer(repo, link.bKm, startKm.CoordinateSystem, startKm.Direction)
if err != nil || linkBKm.Direction != startKm.Direction {
continue
}
//记录公里标最小/最大的Link
newMinKmValue := math.Min(float64(linkAKm.Value), float64(linkBKm.Value))
newMaxKmValue := math.Max(float64(linkAKm.Value), float64(linkBKm.Value))
if minKmLink == nil || newMinKmValue < minKmValue {
minKmValue = newMinKmValue
minKmLink = link
}
if maxKmLink == nil || newMaxKmValue > maxKmValue {
maxKmValue = newMaxKmValue
maxKmLink = link
}
//若start/end公里标落在link上构建LinkPosition
if number.IsBetween(startKm.Value, linkAKm.Value, linkBKm.Value) {
start = &LinkPosition{
link: link,
offset: int64(math.Abs(float64(startKm.Value - linkAKm.Value))),
}
}
if number.IsBetween(endKm.Value, linkAKm.Value, linkBKm.Value) {
end = &LinkPosition{
link: link,
offset: int64(math.Abs(float64(endKm.Value - linkAKm.Value))),
}
}
if start != nil && end != nil {
break
}
}
//处理start/end公里标没落在Link上的情况
if start == nil {
if startKm.Value < endKm.Value {
offset, _ := minKmLink.findEndOffset()
start = &LinkPosition{
link: minKmLink,
offset: offset,
}
} else {
offset, _ := maxKmLink.findEndOffset()
start = &LinkPosition{
link: maxKmLink,
offset: offset,
}
}
}
if end == nil {
if endKm.Value < startKm.Value {
offset, _ := minKmLink.findEndOffset()
end = &LinkPosition{
link: minKmLink,
offset: offset,
}
} else {
offset, _ := maxKmLink.findEndOffset()
end = &LinkPosition{
link: maxKmLink,
offset: offset,
}
}
}
return start, end, nil
}
func sectionalCurvatureRelateLink(repo *Repository) error {
for _, curvature := range repo.sectionalCurvatureMap {
start, end, err := calculateLinkSegment(repo, curvature.kms[0], curvature.kms[1])
if err != nil {
return err
}
curvature.bindStartLinkPosition(start)
curvature.bindEndLinkPosition(end)
}
return nil
}