using System; using System.Collections.Generic; using System.Globalization; using System.IO; using Newtonsoft.Json.Linq; namespace StandardScene.MagCarSimulator { public sealed class SimpleLiteSite { public int Id { get; set; } public string Name { get; set; } public double X { get; set; } public double Y { get; set; } public int? TagValue { get; set; } public bool NeedStop { get; set; } public IReadOnlyDictionary Fields { get; set; } } public sealed class SimpleLiteTrack { public int Id { get; set; } public int SiteA { get; set; } public int SiteB { get; set; } public int Direction { get; set; } } /// 解析 SimpleLite maps/*.json 的 Sites / Tracks。 public sealed class SimpleLiteMap { public const string NeedStopField = "Mag_NeedStop"; public string FilePath { get; private set; } public string FileName { get; private set; } public IReadOnlyDictionary Sites { get; private set; } public IReadOnlyList Tracks { get; private set; } public IReadOnlyDictionary> Adjacency { get; private set; } private readonly HashSet _singleOccupancySites = new HashSet(); public static SimpleLiteMap Load(string path, bool useTagValueAsNode) { if (string.IsNullOrWhiteSpace(path) || !File.Exists(path)) { throw new FileNotFoundException("找不到 SimpleLite 地图文件", path); } var root = JObject.Parse(File.ReadAllText(path)); var sites = ParseSites(root["Sites"] as JObject); var tracks = ParseTracks(root["Tracks"] as JObject); var adjacency = BuildAdjacency(sites, tracks); var map = new SimpleLiteMap { FilePath = Path.GetFullPath(path), FileName = Path.GetFileName(path), Sites = sites, Tracks = tracks, Adjacency = adjacency, UseTagValueAsNode = useTagValueAsNode }; map.ResetSingleOccupancyFromStops(); return map; } public bool UseTagValueAsNode { get; private set; } public bool TryGetSite(int siteId, out SimpleLiteSite site) { return Sites.TryGetValue(siteId, out site); } public ushort ResolveNode(int siteId) { if (!Sites.TryGetValue(siteId, out var site)) { return (ushort)Math.Clamp(siteId, 0, ushort.MaxValue); } if (UseTagValueAsNode && site.TagValue.HasValue && site.TagValue.Value >= 0) { return (ushort)Math.Clamp(site.TagValue.Value, 0, ushort.MaxValue); } return (ushort)Math.Clamp(site.Id, 0, ushort.MaxValue); } public bool NeedStop(int siteId) { return Sites.TryGetValue(siteId, out var site) && site.NeedStop; } /// 停止点或交管点:同时只允许一辆。普通站可叠车。 public bool SingleOccupancy(int siteId) { return _singleOccupancySites.Contains(siteId); } public IReadOnlyList ListSingleOccupancySiteIds() { var list = new List(_singleOccupancySites); list.Sort(); return list; } public void ResetSingleOccupancyFromStops() { _singleOccupancySites.Clear(); foreach (var site in Sites.Values) { if (site.NeedStop) { _singleOccupancySites.Add(site.Id); } } } public void AddSingleOccupancySites(IEnumerable siteIds) { if (siteIds == null) { return; } foreach (var id in siteIds) { if (id > 0 && Sites.ContainsKey(id)) { _singleOccupancySites.Add(id); } } } public IReadOnlyList ListNeedStopSiteIds() { var list = new List(); foreach (var site in Sites.Values) { if (site.NeedStop) { list.Add(site.Id); } } list.Sort(); return list; } private static Dictionary ParseSites(JObject sitesNode) { var result = new Dictionary(); if (sitesNode == null) { return result; } foreach (var property in sitesNode.Properties()) { if (property.Value is not JObject obj) { continue; } var id = obj.Value("id") ?? ParseInt(property.Name); if (id <= 0) { continue; } var fields = ReadFields(obj["fields"] as JObject); var site = new SimpleLiteSite { Id = id, Name = obj.Value("name") ?? "", X = obj.Value("x") ?? 0, Y = obj.Value("y") ?? 0, TagValue = TryReadInt(fields, "TagValue"), NeedStop = IsNeedStop(fields), Fields = fields }; result[id] = site; } return result; } private static List ParseTracks(JObject tracksNode) { var result = new List(); if (tracksNode == null) { return result; } foreach (var property in tracksNode.Properties()) { if (property.Value is not JObject obj) { continue; } var siteA = obj.Value("siteA") ?? obj.Value("_siteA") ?? 0; var siteB = obj.Value("siteB") ?? obj.Value("_siteB") ?? 0; if (siteA <= 0 || siteB <= 0) { continue; } result.Add(new SimpleLiteTrack { Id = obj.Value("id") ?? ParseInt(property.Name), SiteA = siteA, SiteB = siteB, Direction = obj.Value("direction") ?? 0 }); } return result; } private static Dictionary> BuildAdjacency( Dictionary sites, List tracks) { var mutable = new Dictionary>(); foreach (var id in sites.Keys) { mutable[id] = new List(); } foreach (var track in tracks) { AddEdge(mutable, track.SiteA, track.SiteB, track.Direction); } var result = new Dictionary>(); foreach (var pair in mutable) { result[pair.Key] = pair.Value; } return result; } private static void AddEdge(Dictionary> graph, int from, int to, int direction) { // 0 双向;1 仅 A→B;2 仅 B→A。其它值按双向处理。 var aToB = direction != 2; var bToA = direction != 1; if (aToB) { AddUnique(graph, from, to); } if (bToA) { AddUnique(graph, to, from); } } private static void AddUnique(Dictionary> graph, int from, int to) { if (!graph.TryGetValue(from, out var list)) { list = new List(); graph[from] = list; } if (!list.Contains(to)) { list.Add(to); } } private static Dictionary ReadFields(JObject fields) { var result = new Dictionary(StringComparer.OrdinalIgnoreCase); if (fields == null) { return result; } foreach (var property in fields.Properties()) { result[property.Name] = property.Value?.Type == JTokenType.Null ? "" : property.Value.ToString(); } return result; } private static bool IsNeedStop(Dictionary fields) { if (!fields.TryGetValue(NeedStopField, out var raw)) { return false; } raw = (raw ?? "").Trim(); return raw.Equals("true", StringComparison.OrdinalIgnoreCase) || raw.Equals("1", StringComparison.OrdinalIgnoreCase) || raw.Equals("yes", StringComparison.OrdinalIgnoreCase); } private static int? TryReadInt(Dictionary fields, string key) { if (!fields.TryGetValue(key, out var raw) || string.IsNullOrWhiteSpace(raw)) { return null; } return int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) ? value : (int?)null; } private static int ParseInt(string text) { return int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) ? value : 0; } } }