| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.ComponentModel; |
| | | using System.ComponentModel.DataAnnotations; |
| | | using System.ComponentModel.Design; |
| | | using System.Data; |
| | | using System.Linq; |
| | |
| | | using System.Threading.Tasks; |
| | | using Model.ModelDto.BllSoDto; |
| | | using Model.ModelDto.SysDto; |
| | | using Serilog; |
| | | using SqlSugar; |
| | | using WMS.Entity.BllAsnEntity; |
| | | using WMS.Entity.BllCheckEntity; |
| | |
| | | using WMS.Entity.Context; |
| | | using WMS.Entity.LogEntity; |
| | | using WMS.Entity.SysEntity; |
| | | |
| | | |
| | | namespace WMS.DAL |
| | | { |
| | |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取小车路径 |
| | | /// </summary> |
| | | /// <param name="startLocation">起始位置</param> |
| | | /// <param name="endLocation">目标位置</param> |
| | | /// <param name="moveType">移动类型 0:移动 1:取货 2:放货</param> |
| | | /// <param name="isLoad">是否载货0:未载货 1:已载货</param> |
| | | /// <returns></returns> |
| | | public static List<CarModel> GetCarPath(string startLocation, string endLocation, int moveType, string isLoad = "0") |
| | | { |
| | | var _db = DataContext.Db; |
| | | if (string.IsNullOrEmpty(startLocation) || string.IsNullOrEmpty(endLocation)) |
| | | { |
| | | return null; |
| | | } |
| | | |
| | | // 起始位置 |
| | | CarModel start = new CarModel() |
| | | { |
| | | X = int.Parse(startLocation.Substring(0, 2)), |
| | | Y = int.Parse(startLocation.Substring(2, 2)), |
| | | Z = int.Parse(startLocation.Substring(4, 2)), |
| | | NodeCom = 0 |
| | | }; |
| | | |
| | | // 目标位置 |
| | | CarModel end = new CarModel() |
| | | { |
| | | X = int.Parse(endLocation.Substring(0, 2)), |
| | | Y = int.Parse(endLocation.Substring(2, 2)), |
| | | Z = int.Parse(endLocation.Substring(4, 2)), |
| | | NodeCom = 0 |
| | | }; |
| | | |
| | | // 获取储位表信息存储到集合里 |
| | | var layer = int.Parse(startLocation.Substring(4, 2)); |
| | | |
| | | // 获取当前层储位信息 |
| | | var locationModels = _db.Queryable<WCSStorageLocat>() |
| | | .Where(m => m.WareHouseNo == "W01" && m.Layer == layer && m.IsDelete == false) |
| | | .ToList(); |
| | | |
| | | #region 不用绘制地图,直接根据储位表计算 liudl |
| | | // 绘制仓库地图 -1:不可用 0:主通道 1:子通道 |
| | | //int[,] warehouseMap = new int[maxRow, maxColumn]; |
| | | //for (int row = 0; row < maxRow; row++) |
| | | //{ |
| | | // for (int column = 0; column < maxColumn; column++) |
| | | // { |
| | | // // 获取当前位置储位信息 |
| | | // var locationModel = locationModels.First(it => it.Row == row && it.Column == column); |
| | | |
| | | // // 不存在此位置信息 |
| | | // if (locationModel == null) |
| | | // { |
| | | // warehouseMap[row, column] = -1; |
| | | // continue; |
| | | // } |
| | | |
| | | // // 储位状态为损坏不可通行 储位状态为屏蔽为可通行不可存储托盘 |
| | | // if (locationModel.Flag == "2") |
| | | // { |
| | | // warehouseMap[row, column] = -1; |
| | | // continue; |
| | | // } |
| | | |
| | | // warehouseMap[row, column] = int.Parse(locationModel.Make); |
| | | // } |
| | | //} |
| | | #endregion |
| | | |
| | | #region 使用算法计算小车路径 |
| | | try |
| | | { |
| | | // 定义开发列表存储路径节点 |
| | | var openSet = new SortedSet<(int fscore, CarModel pos)>(); |
| | | // 定义关闭节点字典 |
| | | var closeSet = new Dictionary<CarModel, CarModel>(); |
| | | // 定义上一位置与目标位置字典 |
| | | var cameFrom = new Dictionary<CarModel, CarModel>(); |
| | | // 定义上一位置与目标位置的实际距离字典 |
| | | var gScore = new Dictionary<CarModel, int>(); |
| | | // 定义上一位置与目标位置的预估距离字典 |
| | | var fScore = new Dictionary<CarModel, int>(); |
| | | |
| | | // 存储最优距离,及起始节点 |
| | | openSet.Add((Heuristic(start, end), start)); |
| | | gScore[start] = 0; |
| | | fScore[start] = Heuristic(start, end); |
| | | |
| | | // 循环查找路径 |
| | | while (openSet.Count > 0) |
| | | { |
| | | var current = openSet.Min.pos; |
| | | openSet.Remove(openSet.Min); |
| | | |
| | | if (current.Equals(end)) |
| | | { |
| | | Log.Error(ReconstructPath(cameFrom, current).ToString()); |
| | | return ReconstructPath(cameFrom, current); |
| | | } |
| | | |
| | | // 存储小车可运行的方向 |
| | | var validDirections = new List<CarModel>(); |
| | | var currentLocation = locationModels.FirstOrDefault(m => m.Row == current.X && m.Column == current.Y); |
| | | if (currentLocation.Make == "0") |
| | | { |
| | | // 主通道 |
| | | validDirections.Add(new CarModel() { X = 1, Y = 0 }); // 右 |
| | | validDirections.Add(new CarModel() { X = -1, Y = 0 }); // 左 |
| | | validDirections.Add(new CarModel() { X = 0, Y = 1 }); // 下 |
| | | validDirections.Add(new CarModel() { X = 0, Y = -1 }); // 上 |
| | | } |
| | | |
| | | if (currentLocation.Make == "1" ) |
| | | { |
| | | // 子通道 |
| | | // 先拆分出口 |
| | | var outNode = currentLocation.AisleOne; |
| | | if (string.IsNullOrEmpty(outNode)) |
| | | { |
| | | throw new Exception("当前位置没有维护出口!"); |
| | | } |
| | | int outX = int.Parse(outNode.Substring(0, 2)); |
| | | int outY = int.Parse(outNode.Substring(2, 2)); |
| | | if (current.X == outX) |
| | | { |
| | | validDirections.Add(new CarModel() { X = 0, Y = 1 }); // 下 |
| | | validDirections.Add(new CarModel() { X = 0, Y = -1 }); // 上 |
| | | |
| | | } |
| | | else |
| | | { |
| | | validDirections.Add(new CarModel() { X = 1, Y = 0 }); // 右 |
| | | validDirections.Add(new CarModel() { X = -1, Y = 0 }); // 左 |
| | | } |
| | | } |
| | | |
| | | // 循环连接节点。 |
| | | bool isNextNode = false; |
| | | foreach (var dir in validDirections) |
| | | { |
| | | CarModel neighbor = new CarModel() { X = current.X + dir.X, Y = current.Y + dir.Y ,Z=layer}; |
| | | |
| | | // 验证下一节点位置是否可通行并且判断是否被其他小车占用 |
| | | |
| | | |
| | | // 判断下一节点是否关闭 |
| | | if (closeSet.ContainsKey(neighbor)) |
| | | { |
| | | closeSet[neighbor] = neighbor; |
| | | } |
| | | |
| | | // 当前节点 |
| | | var currentModel = locationModels.FirstOrDefault(it => it.Row == current.X && it.Column == current.Y); |
| | | // 下一节点 |
| | | var locationModel = locationModels.FirstOrDefault(it => it.Row == neighbor.X && it.Column == neighbor.Y); |
| | | |
| | | // 不存在此位置信息 |
| | | if (locationModel == null) |
| | | { |
| | | closeSet[neighbor] = neighbor; |
| | | continue; |
| | | } |
| | | // 储位状态为损坏不可通行 储位状态为屏蔽为可通行不可存储托盘 |
| | | if (locationModel.Flag == "2") |
| | | { |
| | | closeSet[neighbor] = neighbor; |
| | | continue; |
| | | } |
| | | // 判断下一节点上是否有托盘 |
| | | if (!string.IsNullOrEmpty(locationModel.PalletNo)) |
| | | { |
| | | // 判断小车是否载托盘盘 |
| | | if (isLoad == "1") |
| | | { |
| | | closeSet[neighbor] = neighbor; |
| | | // 小车上载托盘不可通行跳过 |
| | | continue; |
| | | } |
| | | } |
| | | // 优化项,验证下一节点是否被别的小车占用 liudl:在此添加优化代码 |
| | | |
| | | // 更新实际距离 |
| | | int tentativeGScore = gScore[current] + 1; |
| | | // 判断位置是否已包含在路径内 且 是否更近节点 |
| | | if (!gScore.ContainsKey(neighbor) || tentativeGScore < gScore[neighbor]) |
| | | { |
| | | neighbor.IsSendPlc = false; |
| | | // 补充参数 |
| | | if (neighbor.Equals(end)) |
| | | { |
| | | neighbor.NodeCom = moveType; |
| | | neighbor.IsSendPlc = true; |
| | | } |
| | | else if (currentModel.Make != locationModel.Make) |
| | | { |
| | | if (current.X == neighbor.X) |
| | | { |
| | | neighbor.NodeCom = 3; |
| | | } |
| | | else if (current.Y == neighbor.Y) |
| | | { |
| | | neighbor.NodeCom = 2; |
| | | } |
| | | |
| | | neighbor.IsSendPlc = true; |
| | | } |
| | | |
| | | // 更新实际距离与预估距离 |
| | | cameFrom[neighbor] = current; |
| | | gScore[neighbor] = tentativeGScore; |
| | | fScore[neighbor] = tentativeGScore + Heuristic(neighbor, end); |
| | | openSet.Add((fScore[neighbor], neighbor)); |
| | | isNextNode = true; |
| | | } |
| | | } |
| | | |
| | | if (!isNextNode) |
| | | { |
| | | closeSet[current] = current; |
| | | } |
| | | } |
| | | #endregion |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | throw ex; |
| | | } |
| | | |
| | | |
| | | return null; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 计算曼哈顿距离 |
| | | /// </summary> |
| | | /// <param name="start">起始位置</param> |
| | | /// <param name="end">目标位置</param> |
| | | /// <returns>位置距离</returns> |
| | | private static int Heuristic(CarModel start, CarModel end) |
| | | { |
| | | return Math.Abs(start.X - end.X) + Math.Abs(start.Y - end.Y); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 重构完整路径 |
| | | /// </summary> |
| | | /// <param name="cameFrom"></param> |
| | | /// <param name="current"></param> |
| | | /// <returns></returns> |
| | | private static List<CarModel> ReconstructPath(Dictionary<CarModel, CarModel> cameFrom, CarModel current) |
| | | { |
| | | var path = new List<CarModel> { current }; |
| | | while (cameFrom.ContainsKey(current)) |
| | | { |
| | | current = cameFrom[current]; |
| | | path.Insert(0, current); |
| | | } |
| | | return path; |
| | | } |
| | | |
| | | } |
| | | public enum InOutFlag |
| | | { |
| | |
| | | [Description("质检请验单")] |
| | | QC |
| | | } |
| | | |
| | | public class CarModel : IComparable<CarModel> |
| | | { |
| | | /// <summary> |
| | | /// 行=X |
| | | /// </summary> |
| | | public int X { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 列=Y |
| | | /// </summary> |
| | | public int Y { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 层=Z |
| | | /// </summary> |
| | | public int Z { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 节点命令 1:顶货 2:子通道运行 3:主通道运行 4:放货 |
| | | /// </summary> |
| | | public int NodeCom { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 是否下发plc |
| | | /// </summary> |
| | | public bool IsSendPlc { get; set; } |
| | | |
| | | public int CompareTo(CarModel other) |
| | | { |
| | | if (other == null) |
| | | return 1; |
| | | |
| | | // 这里根据 X、Y、Z 坐标进行比较 |
| | | int result = X.CompareTo(other.X); |
| | | if (result != 0) |
| | | return result; |
| | | |
| | | result = Y.CompareTo(other.Y); |
| | | if (result != 0) |
| | | return result; |
| | | |
| | | return Z.CompareTo(other.Z); |
| | | } |
| | | |
| | | // 重写 Equals 方法 |
| | | public override bool Equals(object obj) |
| | | { |
| | | if (obj == null || GetType() != obj.GetType()) |
| | | return false; |
| | | |
| | | CarModel other = (CarModel)obj; |
| | | return X == other.X && Y == other.Y && Z == other.Z; |
| | | } |
| | | |
| | | // 重写 GetHashCode 方法 |
| | | public override int GetHashCode() |
| | | { |
| | | return HashCode.Combine(X, Y, Z); |
| | | } |
| | | } |
| | | |
| | | } |