package memory import ( "fmt" "math" "sort" "strconv" "strings" "sync" "joylink.club/rtsssimulation/repository" proto2 "joylink.club/rtsssimulation/repository/model/proto" "google.golang.org/protobuf/proto" "joylink.club/bj-rtsts-server/db/dbquery" "joylink.club/bj-rtsts-server/db/model" "joylink.club/bj-rtsts-server/dto" "joylink.club/bj-rtsts-server/sys_error" "joylink.club/bj-rtsts-server/ts/protos/graphicData" "joylink.club/bj-rtsts-server/ts/protos/state" ) var ( giTypeMap sync.Map giNameMap sync.Map giDataMap sync.Map ) // 将发布的地图数据放入内存中 func PublishMapVerifyStructure(graphic *model.PublishedGi) { giTypeMap.Store(graphic.ID, graphicData.PictureType(graphic.Type)) giNameMap.Store(graphic.Name, graphic.ID) var message proto.Message switch graphicData.PictureType(graphic.Type) { case graphicData.PictureType_StationLayout: message = &graphicData.RtssGraphicStorage{} case graphicData.PictureType_RelayCabinetLayout: message = &graphicData.RelayCabinetGraphicStorage{} case graphicData.PictureType_Psl: message = &graphicData.PslGraphicStorage{} case graphicData.PictureType_IBP: message = &graphicData.IBPGraphicStorage{} } err := proto.Unmarshal(graphic.Proto, message) if err != nil { panic(&dto.ErrorDto{Code: dto.LogicError, Message: fmt.Sprintf("[id:%d]proto数据反序列化失败:%s", graphic.ID, err)}) } giDataMap.Store(graphic.ID, message) // 初始化地图结构 switch graphicData.PictureType(graphic.Type) { case graphicData.PictureType_StationLayout: graphicStorage := message.(*graphicData.RtssGraphicStorage) giUidMap.Store(graphic.ID, initStationUid(graphicStorage)) case graphicData.PictureType_RelayCabinetLayout: graphicStorage := message.(*graphicData.RelayCabinetGraphicStorage) giUidMap.Store(graphic.ID, initRelayCabinetUid(graphicStorage)) } } func GenerateElementUid(city, lineId string, stationIndexList []string, code string) string { sort.Strings(stationIndexList) var idArr []string idArr = append(idArr, city, lineId) idArr = append(idArr, stationIndexList...) idArr = append(idArr, code) return strings.Join(idArr, "_") } // 移除内存中的地图信息 func DeleteMapVerifyStructure(mapId int32) { giTypeMap.Delete(mapId) giDataMap.Delete(mapId) giUidMap.Delete(mapId) } func QueryGiType(mapId int32) graphicData.PictureType { value, ok := giTypeMap.Load(mapId) if !ok { graphic, err := dbquery.PublishedGi.Debug().Where(dbquery.PublishedGi.ID.Eq(mapId)).First() // 当缓存缺失新地图时,查询一次 if err != nil { panic(&dto.ErrorDto{Code: dto.LogicError, Message: fmt.Sprintf("[mapId:%d]类型映射错误 :%v", mapId, value)}) } PublishMapVerifyStructure(graphic) return graphicData.PictureType(graphic.Type) } return value.(graphicData.PictureType) } func QueryGiData[T proto.Message](mapId int32) T { value, _ := giDataMap.Load(mapId) return value.(T) } func QueryGiId(name string) int32 { value, _ := giNameMap.Load(name) return value.(int32) } // 根据区段,道岔偏移量返回linkID和link相对偏移量 func QueryEcsLinkByDeviceInfo(repo *repository.Repository, mapId int32, status *state.TrainState) (int32, int64, bool, bool, int64) { id := status.HeadDeviceId if status.DevicePort == "" { uid := QueryUidByMidAndComId(mapId, id, &graphicData.Section{}) return sectionMapToEcsLink(repo, uid, status) } else { uid := QueryUidByMidAndComId(mapId, id, &graphicData.Turnout{}) return turnoutMapToEcsLink(repo, uid, status) } } func getStorageIBPMapData(mapCode string) *graphicData.IBPGraphicStorage { // 处理关联的IBP盘信息 if mapCode == "" { return nil } ibpId, ok := giNameMap.Load(mapCode) if !ok { return nil } ibpMapData, ok := giDataMap.Load(ibpId) if !ok { return nil } return ibpMapData.(*graphicData.IBPGraphicStorage) } // 根据物理区段上的偏移量(基于区段A端),找到所在link的linkId与偏移量 func sectionMapToEcsLink(repo *repository.Repository, id string, status *state.TrainState) (int32, int64, bool, bool, int64) { runDirection := status.RunDirection section := repo.FindPhysicalSection(id) if section == nil { panic(sys_error.New(fmt.Sprintf("地图不存在uid:%s缓存", id))) } ao, bo := section.ALinkPosition().Offset(), section.BLinkPosition().Offset() // 检查区段长度是否足够放下列车 status.HeadOffset = checkDeviceContainTrain(status.HeadOffset, status.TrainLength, bo-ao, id) link := section.ALinkPosition().Link() // 是否从A到B,统一坐标 ak, bk := convertRepoBaseKm(repo, section.AKilometer()), convertRepoBaseKm(repo, section.BKilometer()) akv, bkv := ak.Value, bk.Value // 上行 var up, abDirection bool if runDirection { abDirection = akv < bkv if abDirection { up = ao < bo } else { up = ao > bo } } else { abDirection = akv > bkv if abDirection { up = ao < bo } else { up = ao > bo } } linkId, _ := strconv.Atoi(link.Identity.Id()) trainKilometer := concertTrainKilometer(akv, status.HeadOffset, up) if ao < bo { return int32(linkId), ao + status.HeadOffset, up, abDirection, trainKilometer } else { return int32(linkId), ao - status.HeadOffset, up, abDirection, trainKilometer } } // 根据道岔上的偏移量(基于岔心位置),找到所在link的linkId与偏移量 func turnoutMapToEcsLink(repo *repository.Repository, id string, status *state.TrainState) (int32, int64, bool, bool, int64) { port, runDirection := status.DevicePort, status.RunDirection turnout := repo.FindTurnout(id) if turnout == nil { panic(sys_error.New(fmt.Sprintf("不存在道岔【uid:%s】", id))) } var portPosition *repository.LinkPosition var crossKm, portKm *proto2.Kilometer switch port { case "A": portPosition = turnout.FindLinkPositionByPort(proto2.Port_A) portKm = turnout.GetTurnoutKm(proto2.Port_A) case "B": portPosition = turnout.FindLinkPositionByPort(proto2.Port_B) portKm = turnout.GetTurnoutKm(proto2.Port_B) case "C": portPosition = turnout.FindLinkPositionByPort(proto2.Port_C) portKm = turnout.GetTurnoutKm(proto2.Port_C) default: panic(sys_error.New(fmt.Sprintf("无效端口【%s】偏移量", port))) } // 岔心公里标 crossKm = turnout.GetTurnoutKm(proto2.Port_None) portKm, err := repo.ConvertKilometer(portKm, crossKm.CoordinateSystem) if err != nil { panic(sys_error.New("公里标转换出错", err)) } // 检查link偏移 status.HeadOffset = checkDeviceContainTrain(status.HeadOffset, status.TrainLength, crossKm.Value-portKm.Value, id) // 关联link link := portPosition.Link() isStart := link.ARelation().Device().Id() == id up := runDirection if (portKm.Value > crossKm.Value) != isStart { up = !runDirection } pointTo := (portKm.Value > crossKm.Value) == runDirection trainKilometer := concertTrainKilometer(crossKm.Value, status.HeadOffset, pointTo) linkId, _ := strconv.Atoi(link.Identity.Id()) if isStart { return int32(linkId), status.HeadOffset, up, pointTo, trainKilometer } else { // 道岔长度 turnoutLen := int64(math.Abs(float64(portKm.Value - crossKm.Value))) return int32(linkId), portPosition.Offset() + turnoutLen - status.HeadOffset, up, pointTo, trainKilometer } } // 根据linkID和link相对偏移量返回区段,道岔偏移量 // 设备ID、端口、偏移量、上下行、AB走向 func QueryDeviceByCalcLink(repo *repository.Repository, id string, offset int64, up bool) ( deviceId, port string, deviceOffset int64, runDirection, pointTo bool, km int64) { link := repo.FindLink(id) if link == nil { panic(dto.ErrorDto{Code: dto.DataNotExist, Message: fmt.Sprintf("未找到link【%s】", id)}) } if offset > link.Length() { panic(dto.ErrorDto{Code: dto.DataNotExist, Message: fmt.Sprintf("偏移【%d】超出link范围【%d】", offset, link.Length())}) } // 判断是否在道岔上 onTurnout, isA := isOnLinkTurnout(link, offset) if onTurnout { return ecsLinkMapToTurnout(repo, isA, offset, up, link) } else { return ecsLinkMapToSection(repo, offset, up, link) } } // 是否在link的道岔上 func isOnLinkTurnout(link *repository.Link, offset int64) (bool, bool) { aTp := link.ARelation() var turnoutOffset int64 if aTp != nil { turnoutOffset = aTp.Turnout().FindLinkPositionByPort(aTp.Port()).Offset() } if offset <= turnoutOffset { return true, true } bTp := link.BRelation() if bTp != nil { turnoutOffset = bTp.Turnout().FindLinkPositionByPort(bTp.Port()).Offset() return offset >= turnoutOffset, false } return false, false } // 处理在道岔上link映射 func ecsLinkMapToTurnout(repo *repository.Repository, isA bool, offset int64, up bool, link *repository.Link) ( deviceId, port string, deviceOffset int64, runDirection, pointTo bool, km int64) { tp := link.ARelation() if !isA { tp = link.BRelation() } deviceId = tp.Turnout().Id() tpOffset := tp.Turnout().FindLinkPositionByPort(tp.Port()).Offset() crossKmInfo, portKmInfo := tp.Turnout().GetTurnoutKm(proto2.Port_None), tp.Turnout().GetTurnoutKm(tp.Port()) portKmInfo, err := repo.ConvertKilometer(portKmInfo, crossKmInfo.CoordinateSystem) if err != nil { panic(err) } crossKm, portKm := crossKmInfo.Value, portKmInfo.Value if isA { deviceOffset = tpOffset - (tpOffset - offset) pointTo = !up } else { deviceOffset = link.Length() - offset pointTo = up } // 查询公里标大于端口公里标 if crossKm > portKm { km = crossKm - deviceOffset } else { km = crossKm + deviceOffset } if up && isA { // link offset 趋大,A端岔心 ---> 边界 runDirection = crossKm < portKm } else if up && !isA { // link offset 趋大,B端边界 ---> 岔心 runDirection = crossKm > portKm } else if !up && isA { // link offset 趋小,A端边界 ---> 岔心 runDirection = crossKm > portKm } else { // link offset 趋小,B端岔心 ---> 边界 runDirection = crossKm < portKm } switch tp.Port() { case proto2.Port_A: port = "A" case proto2.Port_B: port = "B" case proto2.Port_C: port = "C" } return } // 处理在道岔上link映射 func ecsLinkMapToSection(repo *repository.Repository, offset int64, up bool, link *repository.Link) ( deviceId, port string, deviceOffset int64, runDirection, pointTo bool, km int64) { var section *repository.PhysicalSection for _, s := range link.PhysicalSections() { ao, bo := s.ALinkPosition().Offset(), s.BLinkPosition().Offset() if (ao < offset && offset <= bo) || (bo < offset && offset <= ao) { section = s break } } if section == nil { panic(dto.ErrorDto{Code: dto.DataNotExist, Message: fmt.Sprintf("未找到link设备偏移【%d】", offset)}) } // 元素ID deviceId = section.Id() // link偏移变大方向 ao, bo := section.ALinkPosition().Offset(), section.BLinkPosition().Offset() ak, bk := convertRepoBaseKm(repo, section.AKilometer()), convertRepoBaseKm(repo, section.BKilometer()) akv, bkv := ak.Value, bk.Value if up { if ao < bo { runDirection = akv < bkv } else { runDirection = akv > bkv } } else { if ao > bo { runDirection = akv < bkv } else { runDirection = akv > bkv } } pointTo = runDirection == (akv < bkv) // a点偏移 大于 b点偏移 if ao > bo { deviceOffset = ao - offset } else { deviceOffset = offset - ao } // a点公里标 大于 b点公里标 if akv > bkv { km = akv - deviceOffset } else { km = akv + deviceOffset } return } // 查找列车的公里标 func concertTrainKilometer(kilometer, offset int64, tendTo bool) int64 { if tendTo { return kilometer - offset } else { return kilometer + offset } } // 转换成统一坐标公里标 func convertRepoBaseKm(r *repository.Repository, km *proto2.Kilometer) *proto2.Kilometer { k, err := r.ConvertKilometer(km, r.GetCoordinateInfo().Coordinate) if err != nil { panic(err) } return k } // 检查列车是否可以放到设备上 func checkDeviceContainTrain(offset, trainLen, deviceLen int64, deviceName string) int64 { l := int64(math.Abs(float64(deviceLen))) if offset > l { panic(sys_error.New(fmt.Sprintf("偏移【%d】超出【%s】范围【%d】", offset, deviceName, l))) } if trainLen > offset { // 如果列车长度超过设置offset offset = trainLen } if offset > l { panic(sys_error.New(fmt.Sprintf("列车长度【%d】超出【%s】范围【%d】", trainLen, deviceName, l))) } return offset }