rts-sim-module/repository/simulation.go

388 lines
10 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 (
"fmt"
"joylink.club/rtsssimulation/repository/model/proto"
"joylink.club/rtsssimulation/util"
"math"
"strconv"
)
var simId = 1
func BuildSimulation(id string, version string) (*Simulation, error) {
repository, err := getRepository(id, version)
if err != nil {
return nil, err
}
simulation := newSimulation(repository)
//构建link
err = buildLinks(simulation)
if err != nil {
return nil, err
}
//构建Slope的Link区间
err = buildSlopeLinkSegments(simulation)
if err != nil {
return nil, err
}
//构建SectionalCurvature的Link区间
err = buildSectionalCurvatureLinkSegments(simulation)
if err != nil {
return nil, err
}
return simulation, nil
}
func getASimulationId() string {
id := strconv.Itoa(simId)
simId++
return id
}
type Simulation struct {
id string
idGenerator int
repository *Repository
linkMap map[string]*Link
devicePositionMap map[string]*DeviceLinkPosition //key为device的id
linkNodeMap map[string]*LinkNode //LinkNode的id应该没什么意义所以此处key用LinkNode中Turnout的id
slopeLinkSegmentMap map[string]SlopeLinkSegment
sectionalCurvatureLinkSegmentMap map[string]SectionalCurvatureLinkSegment
}
func newSimulation(repository *Repository) *Simulation {
return &Simulation{
id: getASimulationId(),
idGenerator: 1,
repository: repository,
linkMap: make(map[string]*Link),
devicePositionMap: make(map[string]*DeviceLinkPosition),
linkNodeMap: make(map[string]*LinkNode),
slopeLinkSegmentMap: make(map[string]SlopeLinkSegment),
sectionalCurvatureLinkSegmentMap: make(map[string]SectionalCurvatureLinkSegment),
}
}
func (s *Simulation) getAId() string {
id := strconv.Itoa(s.idGenerator)
s.idGenerator++
return id
}
func buildLinks(sim *Simulation) error {
visitedTurnoutPortMap := make(map[string]bool)
allTurnouts := sim.repository.TurnoutList()
for {
//找一个未遍历过的道岔端口
startTp := getATurnoutPort(allTurnouts, visitedTurnoutPortMap)
if startTp == nil {
break
}
//以始端道岔的公里标作为公里标换算的基准
baseKm := startTp.turnout.km
//创建基础Link
link := &Link{Identity: identity{sim.getAId(), proto.DeviceType_DeviceType_Link}}
//以此道岔端口作为Link的A端节点
err := buildAndRelateLinkNode(sim, startTp, link, proto.Port_A)
if err != nil {
return err
}
//沿着道岔端口方向一直寻找到轨道尽头或者下一个道岔端口。构建并关联中间的设备在link上的位置
endTp, endKm, err := findEndTurnoutPortOrEndKm(sim, link, startTp, baseKm)
if err != nil {
return err
}
//以下一个道岔端口作为Link的B端节点
if endTp != nil {
visitedTurnoutPortMap[buildTurnoutPortKey(endTp)] = true
endKm = endTp.turnout.km
err = buildAndRelateLinkNode(sim, endTp, link, proto.Port_B)
if err != nil {
return err
}
}
//计算Link长度
convertedKm, err := convertKilometer(sim.repository, endKm, baseKm.CoordinateSystem, baseKm.Direction)
if err != nil {
return err
}
link.length = int64(math.Abs(float64(convertedKm.Value - baseKm.Value)))
sim.linkMap[link.Id()] = link
}
return nil
}
func findEndTurnoutPortOrEndKm(sim *Simulation, 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.getDevicesByPort(startTp.port)
for {
//遍历设备并构建、关联其在Link上的位置
err = buildAndRelateDeviceLinkPositions(sim, 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)
nextDp = section.findOtherDevicePort(currentDp.Port())
if nextDp == nil {
endKm = section.findOtherBoundaryKmByPort(currentDp.Port())
}
case proto.DeviceType_DeviceType_Turnout:
turnout := currentDp.Device().(*Turnout)
nextDp = turnout.getDevicePortByPort(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).getDevicesByPort(nextDp.Port())
err = buildAndRelateDeviceLinkPositions(sim, link, baseKm, visitedModelMap, devices...)
if err != nil {
return nil, nil, err
}
}
if nextIsTp {
break
}
}
return endTp, endKm, err
}
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 buildAndRelateDeviceLinkPositions(sim *Simulation, 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(sim.repository, km, startKm.CoordinateSystem, startKm.Direction)
if err != nil {
return err
}
offset := int64(math.Abs(float64(convertedKm.Value - startKm.Value)))
dlps := &DeviceLinkPosition{
device: device,
position: LinkPosition{
link: link,
offset: offset,
},
}
sim.devicePositionMap[dlps.device.Id()] = dlps
link.bindDevicesLinkPositions(dlps)
}
return nil
}
func buildAndRelateLinkNode(sim *Simulation, tp *TurnoutPort, link *Link, port proto.Port) error {
ln := sim.linkNodeMap[tp.turnout.Id()]
if ln == nil {
ln = &LinkNode{
Identity: identity{sim.getAId(), proto.DeviceType_DeviceType_LinkNode},
turnout: tp.turnout,
}
}
//LinkNode关联Link
err := ln.bindDevicePort(port, &LinkPort{
link: link,
port: port,
})
if err != nil {
return err
}
//Link关联LinkNode
err = link.bindDevicePort(port, &LinkNodePort{
node: ln,
port: tp.port,
})
return err
}
func findModelKm(model Identity) *proto.Kilometer {
signal, ok := model.(*Signal)
if ok {
return signal.km
}
responder, ok := model.(*Responder)
if ok {
return responder.km
}
cp, ok := model.(*CheckPoint)
if ok {
return cp.km
}
turnout, ok := model.(*Turnout)
if ok {
return turnout.km
}
return nil
}
func buildSlopeLinkSegments(simulation *Simulation) error {
repo := simulation.repository
slopeMap := repo.slopeMap
for _, slope := range slopeMap {
start, end, err := calculateLinkSegment(simulation, slope.kms[0], slope.kms[1])
if err != nil {
return err
}
simulation.slopeLinkSegmentMap[slope.Id()] = SlopeLinkSegment{
slope: slope,
start: start,
end: end,
}
}
return nil
}
func calculateLinkSegment(simulation *Simulation, startKm *proto.Kilometer,
endKm *proto.Kilometer) (*LinkPosition, *LinkPosition, error) {
repo := simulation.repository
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 simulation.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 util.IsBetween(startKm.Value, linkAKm.Value, linkBKm.Value) {
start = &LinkPosition{
link: link,
offset: int64(math.Abs(float64(startKm.Value - linkAKm.Value))),
}
}
if util.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 buildSectionalCurvatureLinkSegments(sim *Simulation) error {
repo := sim.repository
for _, curvature := range repo.sectionalCurvatureMap {
start, end, err := calculateLinkSegment(sim, curvature.kms[0], curvature.kms[1])
if err != nil {
return err
}
sim.sectionalCurvatureLinkSegmentMap[curvature.Id()] = SectionalCurvatureLinkSegment{
sectionalCurvature: curvature,
start: start,
end: end,
}
}
return nil
}