| | |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.thread.StationThread; |
| | | import com.zy.system.entity.Config; |
| | | import com.zy.system.service.ConfigService; |
| | | |
| | | @Component |
| | | public class NavigateUtils { |
| | | |
| | | private static final String CFG_STATION_PATH_LEN_WEIGHT_PERCENT = "stationPathLenWeightPercent"; |
| | | private static final String CFG_STATION_PATH_CONG_WEIGHT_PERCENT = "stationPathCongWeightPercent"; |
| | | private static final String CFG_STATION_PATH_PASS_OTHER_OUT_STATION_WEIGHT_PERCENT = "stationPathPassOtherOutStationWeightPercent"; |
| | | private static final String CFG_STATION_PATH_PASS_OTHER_OUT_STATION_FORCE_SKIP = "stationPathPassOtherOutStationForceSkip"; |
| | | |
| | | @Autowired |
| | | private BasStationService basStationService; |
| | |
| | | |
| | | long startTime = System.currentTimeMillis(); |
| | | News.info("[WCS Debug] 站点路径开始计算,startStationId={},endStationId={}", startStationId, endStationId); |
| | | int calcMaxDepth = resolvedPolicy.useTwoStage() ? safeInt(profileConfig.getCalcMaxDepth(), 120) : 120; |
| | | int calcMaxPaths = resolvedPolicy.useTwoStage() ? safeInt(profileConfig.getCalcMaxPaths(), 500) : 500; |
| | | int calcMaxCost = resolvedPolicy.useTwoStage() ? safeInt(profileConfig.getCalcMaxCost(), 300) : 300; |
| | | List<List<NavigateNode>> allList = navigateSolution.allSimplePaths(stationMap, startNode, endNode, calcMaxDepth, calcMaxPaths, calcMaxCost); |
| | | int calcMaxDepth = safeInt(profileConfig.getCalcMaxDepth(), 120); |
| | | int calcMaxPaths = safeInt(profileConfig.getCalcMaxPaths(), 500); |
| | | int calcMaxCost = safeInt(profileConfig.getCalcMaxCost(), 300); |
| | | List<Integer> guideStationSequence = buildGuideStationSequence(startStationId, endStationId, resolvedPolicy.getRuleConfig()); |
| | | List<List<NavigateNode>> allList = navigateSolution.allSimplePaths( |
| | | stationMap, |
| | | startNode, |
| | | endNode, |
| | | calcMaxDepth, |
| | | calcMaxPaths, |
| | | calcMaxCost, |
| | | guideStationSequence |
| | | ); |
| | | if (allList.isEmpty()) { |
| | | // throw new CoolException("未找到该路径"); |
| | | return new ArrayList<>(); |
| | |
| | | |
| | | startTime = System.currentTimeMillis(); |
| | | News.info("[WCS Debug] 站点路径权重开始分析,startStationId={},endStationId={}", startStationId, endStationId); |
| | | List<NavigateNode> list = resolvedPolicy.useTwoStage() |
| | | ? findStationBestPathTwoStage(allList, resolvedPolicy) |
| | | : findStationBestPath(allList); |
| | | List<NavigateNode> list = findStationBestPathTwoStage(allList, resolvedPolicy); |
| | | News.info("[WCS Debug] 站点路径权重分析完成,耗时:{}ms", System.currentTimeMillis() - startTime); |
| | | |
| | | //去重 |
| | |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | News.warn("站点路径策略加载失败,回退 legacy: {}", e.getMessage()); |
| | | News.warn("站点路径策略加载失败,回退默认 twoStage: {}", e.getMessage()); |
| | | } |
| | | return new StationPathResolvedPolicy(); |
| | | } |
| | |
| | | StationPathProfileConfig profileConfig = resolvedPolicy.getProfileConfig() == null |
| | | ? StationPathProfileConfig.defaultConfig() |
| | | : resolvedPolicy.getProfileConfig(); |
| | | PathGlobalPolicy globalPolicy = loadPathGlobalPolicy(profileConfig); |
| | | |
| | | List<List<NavigateNode>> filteredCandidates = applyRuleFilters(allList, ruleConfig, true); |
| | | if (filteredCandidates.isEmpty() && hasWaypoint(ruleConfig) && !strictWaypoint(ruleConfig)) { |
| | | filteredCandidates = applyRuleFilters(allList, ruleConfig, false); |
| | | News.info("[WCS Debug] 站点路径规则已降级,忽略关键途经点约束后重试"); |
| | | } |
| | | List<List<NavigateNode>> softFilteredCandidates = applySoftPreferenceFilter(filteredCandidates, ruleConfig); |
| | | if (!softFilteredCandidates.isEmpty()) { |
| | | filteredCandidates = softFilteredCandidates; |
| | | } else if (hasSoftPreference(ruleConfig) && !allowSoftDegrade(ruleConfig)) { |
| | | News.warn("[WCS Debug] 站点路径软偏好命中但无可行路径,且不允许降级"); |
| | | return new ArrayList<>(); |
| | | } else if (hasSoftPreference(ruleConfig)) { |
| | | News.info("[WCS Debug] 站点路径规则已降级,忽略软偏好约束后重试"); |
| | | } |
| | | if (filteredCandidates.isEmpty()) { |
| | | if (resolvedPolicy.matchedRule()) { |
| | |
| | | |
| | | Map<Integer, StationProtocol> statusMap = loadStationStatusMap(); |
| | | Map<Integer, Double> stationLoopLoadMap = loadStationLoopLoadMap(); |
| | | Set<Integer> outStationIdSet = loadAllOutStationIdSet(); |
| | | List<PathCandidateMetrics> metricsList = new ArrayList<>(); |
| | | int skippedByOtherOutStation = 0; |
| | | for (List<NavigateNode> path : filteredCandidates) { |
| | | if (path == null || path.isEmpty()) { |
| | | continue; |
| | | } |
| | | metricsList.add(buildCandidateMetrics(path, statusMap, stationLoopLoadMap, profileConfig, ruleConfig)); |
| | | PathCandidateMetrics metrics = buildCandidateMetrics(path, statusMap, stationLoopLoadMap, profileConfig, ruleConfig, globalPolicy, outStationIdSet); |
| | | if (globalPolicy.forceSkipPassOtherOutStation && metrics.passOtherOutStationCount > 0) { |
| | | skippedByOtherOutStation++; |
| | | continue; |
| | | } |
| | | metricsList.add(metrics); |
| | | } |
| | | if (metricsList.isEmpty()) { |
| | | if (globalPolicy.forceSkipPassOtherOutStation && skippedByOtherOutStation > 0) { |
| | | News.warn("[WCS Debug] 站点路径候选全部被过滤,因经过其他出库站点,startStationId={},endStationId={}", |
| | | resolvedPolicy.getRuleEntity() == null ? null : resolvedPolicy.getRuleEntity().getStartStationId(), |
| | | resolvedPolicy.getRuleEntity() == null ? null : resolvedPolicy.getRuleEntity().getEndStationId()); |
| | | } |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | |
| | | continue; |
| | | } |
| | | if (includeWaypoint && !matchWaypointConstraint(stationIdList, ruleConfig.getWaypoint())) { |
| | | continue; |
| | | } |
| | | result.add(path); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private List<List<NavigateNode>> applySoftPreferenceFilter(List<List<NavigateNode>> allList, |
| | | StationPathRuleConfig ruleConfig) { |
| | | if (allList == null || allList.isEmpty() || ruleConfig == null) { |
| | | return new ArrayList<>(); |
| | | } |
| | | StationPathRuleConfig.SoftPreference soft = ruleConfig.getSoft(); |
| | | List<Integer> preferredPath = getSoftReferencePath(soft); |
| | | if (preferredPath.isEmpty()) { |
| | | return allList; |
| | | } |
| | | |
| | | List<List<NavigateNode>> result = new ArrayList<>(); |
| | | for (List<NavigateNode> path : allList) { |
| | | if (path == null || path.isEmpty()) { |
| | | continue; |
| | | } |
| | | List<Integer> stationIdList = extractStationIdList(path); |
| | | if (!matchSoftConstraint(stationIdList, soft)) { |
| | | continue; |
| | | } |
| | | result.add(path); |
| | |
| | | return false; |
| | | } |
| | | |
| | | private boolean matchSoftConstraint(List<Integer> stationIdList, StationPathRuleConfig.SoftPreference soft) { |
| | | List<Integer> preferredPath = getSoftReferencePath(soft); |
| | | if (preferredPath.isEmpty()) { |
| | | return true; |
| | | } |
| | | if (!containsOrderedStations(stationIdList, preferredPath)) { |
| | | return false; |
| | | } |
| | | Integer maxOffPathCount = soft == null ? null : soft.getMaxOffPathCount(); |
| | | if (maxOffPathCount == null || maxOffPathCount < 0) { |
| | | return true; |
| | | } |
| | | if (!isFullSoftPreferredPath(stationIdList, preferredPath)) { |
| | | return true; |
| | | } |
| | | return countOffPathStations(stationIdList, preferredPath) <= maxOffPathCount; |
| | | } |
| | | |
| | | private boolean hasWaypoint(StationPathRuleConfig ruleConfig) { |
| | | return ruleConfig != null |
| | | && ruleConfig.getWaypoint() != null |
| | |
| | | && !ruleConfig.getWaypoint().getStations().isEmpty(); |
| | | } |
| | | |
| | | private boolean hasSoftPreference(StationPathRuleConfig ruleConfig) { |
| | | return !getSoftReferencePath(ruleConfig == null ? null : ruleConfig.getSoft()).isEmpty(); |
| | | } |
| | | |
| | | private boolean strictWaypoint(StationPathRuleConfig ruleConfig) { |
| | | return ruleConfig != null |
| | | && ruleConfig.getFallback() != null |
| | | && Boolean.TRUE.equals(ruleConfig.getFallback().getStrictWaypoint()); |
| | | } |
| | | |
| | | private boolean allowSoftDegrade(StationPathRuleConfig ruleConfig) { |
| | | return ruleConfig == null |
| | | || ruleConfig.getFallback() == null |
| | | || !Boolean.FALSE.equals(ruleConfig.getFallback().getAllowSoftDegrade()); |
| | | } |
| | | |
| | | private List<Integer> buildGuideStationSequence(Integer startStationId, |
| | | Integer endStationId, |
| | | StationPathRuleConfig ruleConfig) { |
| | | if (startStationId == null || endStationId == null || ruleConfig == null) { |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | List<Integer> sequence = new ArrayList<>(); |
| | | appendGuideStation(sequence, startStationId); |
| | | |
| | | List<Integer> preferredPath = safeList(ruleConfig.getSoft() == null ? null : ruleConfig.getSoft().getPreferredPath()); |
| | | if (!preferredPath.isEmpty()) { |
| | | if (startStationId.equals(preferredPath.get(0))) { |
| | | for (int i = 1; i < preferredPath.size(); i++) { |
| | | appendGuideStation(sequence, preferredPath.get(i)); |
| | | } |
| | | if (sequence.get(sequence.size() - 1).equals(endStationId)) { |
| | | return sequence; |
| | | } |
| | | } |
| | | sequence.clear(); |
| | | appendGuideStation(sequence, startStationId); |
| | | } |
| | | |
| | | for (Integer stationId : safeList(ruleConfig.getWaypoint() == null ? null : ruleConfig.getWaypoint().getStations())) { |
| | | appendGuideStation(sequence, stationId); |
| | | } |
| | | for (Integer stationId : safeList(ruleConfig.getSoft() == null ? null : ruleConfig.getSoft().getKeyStations())) { |
| | | appendGuideStation(sequence, stationId); |
| | | } |
| | | appendGuideStation(sequence, endStationId); |
| | | return sequence.size() <= 2 ? Collections.emptyList() : sequence; |
| | | } |
| | | |
| | | private void appendGuideStation(List<Integer> sequence, Integer stationId) { |
| | | if (sequence == null || stationId == null) { |
| | | return; |
| | | } |
| | | if (!sequence.isEmpty() && stationId.equals(sequence.get(sequence.size() - 1))) { |
| | | return; |
| | | } |
| | | sequence.add(stationId); |
| | | } |
| | | |
| | | private boolean containsEdge(List<Integer> stationIdList, String edgeText) { |
| | |
| | | Map<Integer, StationProtocol> statusMap, |
| | | Map<Integer, Double> stationLoopLoadMap, |
| | | StationPathProfileConfig profileConfig, |
| | | StationPathRuleConfig ruleConfig) { |
| | | StationPathRuleConfig ruleConfig, |
| | | PathGlobalPolicy globalPolicy, |
| | | Set<Integer> outStationIdSet) { |
| | | PathCandidateMetrics metrics = new PathCandidateMetrics(); |
| | | metrics.path = path; |
| | | metrics.pathLen = path.size(); |
| | |
| | | metrics.busyStationCount = countBusyStationCount(stationIdList, statusMap); |
| | | metrics.runBlockCount = countRunBlockCount(stationIdList, statusMap); |
| | | metrics.loopPenalty = calcLoopPenalty(stationIdList, stationLoopLoadMap); |
| | | metrics.passOtherOutStationCount = countPassOtherOutStations(path, outStationIdSet); |
| | | metrics.softDeviationCount = calcSoftDeviationCount(stationIdList, |
| | | ruleConfig == null || ruleConfig.getSoft() == null ? null : ruleConfig.getSoft().getPreferredPath()); |
| | | ruleConfig == null ? null : ruleConfig.getSoft()); |
| | | |
| | | double softDeviationWeight = safeDouble(profileConfig.getS1SoftDeviationWeight(), 4.0d); |
| | | if (ruleConfig != null && ruleConfig.getSoft() != null && ruleConfig.getSoft().getDeviationWeight() != null) { |
| | | softDeviationWeight = ruleConfig.getSoft().getDeviationWeight(); |
| | | } |
| | | |
| | | double lenWeightFactor = globalPolicy == null ? 1.0d : globalPolicy.lenWeightFactor; |
| | | double congWeightFactor = globalPolicy == null ? 1.0d : globalPolicy.congWeightFactor; |
| | | double passOtherOutStationPenaltyWeight = globalPolicy == null ? 0.0d : globalPolicy.passOtherOutStationPenaltyWeight; |
| | | |
| | | metrics.staticCost = |
| | | safeDouble(profileConfig.getS1LenWeight(), 1.0d) * metrics.pathLen |
| | | safeDouble(profileConfig.getS1LenWeight(), 1.0d) * lenWeightFactor * metrics.pathLen |
| | | + safeDouble(profileConfig.getS1TurnWeight(), 3.0d) * metrics.turnCount |
| | | + safeDouble(profileConfig.getS1LiftWeight(), 8.0d) * metrics.liftTransferCount |
| | | + passOtherOutStationPenaltyWeight * metrics.passOtherOutStationCount |
| | | + softDeviationWeight * metrics.softDeviationCount; |
| | | |
| | | metrics.dynamicCost = |
| | | safeDouble(profileConfig.getS2BusyWeight(), 2.0d) * metrics.busyStationCount |
| | | safeDouble(profileConfig.getS2BusyWeight(), 2.0d) * congWeightFactor * metrics.busyStationCount |
| | | + safeDouble(profileConfig.getS2RunBlockWeight(), 10.0d) * metrics.runBlockCount |
| | | + safeDouble(profileConfig.getS2LoopLoadWeight(), 12.0d) * metrics.loopPenalty; |
| | | return metrics; |
| | |
| | | return maxLoad; |
| | | } |
| | | |
| | | private int calcSoftDeviationCount(List<Integer> stationIdList, List<Integer> preferredPath) { |
| | | if (preferredPath == null || preferredPath.isEmpty() || stationIdList == null || stationIdList.isEmpty()) { |
| | | private int calcSoftDeviationCount(List<Integer> stationIdList, StationPathRuleConfig.SoftPreference soft) { |
| | | List<Integer> preferredPath = getSoftReferencePath(soft); |
| | | if (preferredPath.isEmpty() || stationIdList == null || stationIdList.isEmpty()) { |
| | | return 0; |
| | | } |
| | | Set<Integer> preferredSet = new HashSet<>(preferredPath); |
| | | int count = 0; |
| | | for (int i = 1; i < stationIdList.size() - 1; i++) { |
| | | Integer stationId = stationIdList.get(i); |
| | | if (stationId != null && !preferredSet.contains(stationId)) { |
| | | count++; |
| | | } |
| | | int missingCount = countMissingOrderedStations(stationIdList, preferredPath); |
| | | if (missingCount > 0) { |
| | | return missingCount; |
| | | } |
| | | return count; |
| | | if (isFullSoftPreferredPath(stationIdList, preferredPath)) { |
| | | return countOffPathStations(stationIdList, preferredPath); |
| | | } |
| | | return 0; |
| | | } |
| | | |
| | | private List<Integer> extractStationIdList(List<NavigateNode> path) { |
| | |
| | | } |
| | | } |
| | | return stationIdList; |
| | | } |
| | | |
| | | private List<Integer> getSoftReferencePath(StationPathRuleConfig.SoftPreference soft) { |
| | | if (soft == null) { |
| | | return Collections.emptyList(); |
| | | } |
| | | if (soft.getPreferredPath() != null && !soft.getPreferredPath().isEmpty()) { |
| | | return soft.getPreferredPath(); |
| | | } |
| | | if (soft.getKeyStations() != null && !soft.getKeyStations().isEmpty()) { |
| | | return soft.getKeyStations(); |
| | | } |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | private boolean containsOrderedStations(List<Integer> stationIdList, List<Integer> targetStations) { |
| | | return countMissingOrderedStations(stationIdList, targetStations) == 0; |
| | | } |
| | | |
| | | private int countMissingOrderedStations(List<Integer> stationIdList, List<Integer> targetStations) { |
| | | if (stationIdList == null || stationIdList.isEmpty() || targetStations == null || targetStations.isEmpty()) { |
| | | return 0; |
| | | } |
| | | int cursor = 0; |
| | | for (Integer stationId : stationIdList) { |
| | | Integer expected = targetStations.get(cursor); |
| | | if (expected != null && expected.equals(stationId)) { |
| | | cursor++; |
| | | if (cursor >= targetStations.size()) { |
| | | return 0; |
| | | } |
| | | } |
| | | } |
| | | return targetStations.size() - cursor; |
| | | } |
| | | |
| | | private boolean isFullSoftPreferredPath(List<Integer> stationIdList, List<Integer> preferredPath) { |
| | | if (stationIdList == null || stationIdList.isEmpty() || preferredPath == null || preferredPath.isEmpty()) { |
| | | return false; |
| | | } |
| | | Integer actualStart = stationIdList.get(0); |
| | | Integer actualEnd = stationIdList.get(stationIdList.size() - 1); |
| | | Integer preferredStart = preferredPath.get(0); |
| | | Integer preferredEnd = preferredPath.get(preferredPath.size() - 1); |
| | | return (actualStart == null ? preferredStart == null : actualStart.equals(preferredStart)) |
| | | && (actualEnd == null ? preferredEnd == null : actualEnd.equals(preferredEnd)); |
| | | } |
| | | |
| | | private int countOffPathStations(List<Integer> stationIdList, List<Integer> preferredPath) { |
| | | if (stationIdList == null || stationIdList.isEmpty() || preferredPath == null || preferredPath.isEmpty()) { |
| | | return 0; |
| | | } |
| | | Set<Integer> preferredSet = new HashSet<>(preferredPath); |
| | | int count = 0; |
| | | for (int i = 1; i < stationIdList.size() - 1; i++) { |
| | | Integer stationId = stationIdList.get(i); |
| | | if (stationId != null && !preferredSet.contains(stationId)) { |
| | | count++; |
| | | } |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | private List<List<NavigateNode>> filterNonAutoStationPaths(List<List<NavigateNode>> allList, |
| | |
| | | private int pathLen; |
| | | private int turnCount; |
| | | private int liftTransferCount; |
| | | private int passOtherOutStationCount; |
| | | private int busyStationCount; |
| | | private int runBlockCount; |
| | | private int softDeviationCount; |
| | |
| | | private double dynamicCost; |
| | | } |
| | | |
| | | public synchronized List<NavigateNode> findStationBestPath(List<List<NavigateNode>> allList) { |
| | | if (allList == null || allList.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | private static class PathGlobalPolicy { |
| | | private double lenWeightFactor = 1.0d; |
| | | private double congWeightFactor = 1.0d; |
| | | private double passOtherOutStationPenaltyWeight = 0.0d; |
| | | private boolean forceSkipPassOtherOutStation = false; |
| | | } |
| | | private PathGlobalPolicy loadPathGlobalPolicy(StationPathProfileConfig profileConfig) { |
| | | PathGlobalPolicy weights = new PathGlobalPolicy(); |
| | | StationPathProfileConfig source = profileConfig == null ? StationPathProfileConfig.defaultConfig() : profileConfig; |
| | | double lenWeightPercent = safeDouble(source.getStationPathLenWeightPercent(), 50.0d); |
| | | double congWeightPercent = safeDouble(source.getStationPathCongWeightPercent(), 50.0d); |
| | | double passOtherOutStationWeightPercent = safeDouble(source.getStationPathPassOtherOutStationWeightPercent(), 100.0d); |
| | | weights.forceSkipPassOtherOutStation = Boolean.TRUE.equals(source.getStationPathPassOtherOutStationForceSkip()); |
| | | |
| | | lenWeightPercent = Math.max(lenWeightPercent, 0.0d); |
| | | congWeightPercent = Math.max(congWeightPercent, 0.0d); |
| | | passOtherOutStationWeightPercent = Math.max(passOtherOutStationWeightPercent, 0.0d); |
| | | double weightSum = lenWeightPercent + congWeightPercent; |
| | | if (weightSum <= 0) { |
| | | weights.passOtherOutStationPenaltyWeight = passOtherOutStationWeightPercent / 100.0d * 8.0d; |
| | | return weights; |
| | | } |
| | | |
| | | Map<Integer, StationProtocol> statusMap = new HashMap<>(); |
| | | try { |
| | | DeviceConfigService deviceConfigService = SpringUtils.getBean(DeviceConfigService.class); |
| | | if (deviceConfigService != null) { |
| | | List<DeviceConfig> devpList = deviceConfigService.list(new QueryWrapper<DeviceConfig>() |
| | | .eq("device_type", String.valueOf(SlaveType.Devp))); |
| | | for (DeviceConfig deviceConfig : devpList) { |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, deviceConfig.getDeviceNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | Map<Integer, StationProtocol> m = stationThread.getStatusMap(); |
| | | if (m != null && !m.isEmpty()) { |
| | | statusMap.putAll(m); |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception ignore) {} |
| | | |
| | | Set<Integer> outStationIdSet = loadAllOutStationIdSet(); |
| | | |
| | | double lenWeightPercent = 50.0; |
| | | double congWeightPercent = 50.0; |
| | | double passOtherOutStationWeightPercent = 100.0; |
| | | boolean forceSkipPassOtherOutStation = false; |
| | | try { |
| | | ConfigService configService = SpringUtils.getBean(ConfigService.class); |
| | | if (configService != null) { |
| | | lenWeightPercent = loadDoubleConfig(configService, CFG_STATION_PATH_LEN_WEIGHT_PERCENT, lenWeightPercent); |
| | | congWeightPercent = loadDoubleConfig(configService, CFG_STATION_PATH_CONG_WEIGHT_PERCENT, congWeightPercent); |
| | | passOtherOutStationWeightPercent = loadDoubleConfig(configService, CFG_STATION_PATH_PASS_OTHER_OUT_STATION_WEIGHT_PERCENT, passOtherOutStationWeightPercent); |
| | | forceSkipPassOtherOutStation = loadBooleanConfig(configService, CFG_STATION_PATH_PASS_OTHER_OUT_STATION_FORCE_SKIP, false); |
| | | } |
| | | } catch (Exception ignore) {} |
| | | |
| | | List<List<NavigateNode>> candidates = new ArrayList<>(); |
| | | List<Integer> lens = new ArrayList<>(); |
| | | List<Integer> tasksList = new ArrayList<>(); |
| | | List<Double> congs = new ArrayList<>(); |
| | | List<Integer> passOtherOutStationCounts = new ArrayList<>(); |
| | | int skippedByPassOtherOutStation = 0; |
| | | |
| | | for (List<NavigateNode> path : allList) { |
| | | if (path == null || path.isEmpty()) { |
| | | continue; |
| | | } |
| | | int len = path.size(); |
| | | int tasks = 0; |
| | | HashSet<Integer> stationIdSet = new HashSet<>(); |
| | | for (NavigateNode node : path) { |
| | | JSONObject value = null; |
| | | try { |
| | | value = JSON.parseObject(node.getNodeValue()); |
| | | } catch (Exception ignore) {} |
| | | if (value == null) { |
| | | continue; |
| | | } |
| | | Integer stationId = value.getInteger("stationId"); |
| | | if (stationId == null) { |
| | | continue; |
| | | } |
| | | if (!stationIdSet.add(stationId)) { |
| | | continue; |
| | | } |
| | | StationProtocol protocol = statusMap.get(stationId); |
| | | if (protocol != null && protocol.getTaskNo() != null && protocol.getTaskNo() > 0) { |
| | | tasks++; |
| | | } |
| | | } |
| | | double cong = len <= 0 ? 0.0 : (double) tasks / (double) len; |
| | | int passOtherOutStationCount = countPassOtherOutStations(path, outStationIdSet); |
| | | if (forceSkipPassOtherOutStation && passOtherOutStationCount > 0) { |
| | | skippedByPassOtherOutStation++; |
| | | News.info("[WCS Debug] 站点路径候选已跳过,因经过其他出库站点,startStationId={},endStationId={},passOtherOutStationCount={}", |
| | | extractStationId(path.get(0)), |
| | | extractStationId(path.get(path.size() - 1)), |
| | | passOtherOutStationCount); |
| | | continue; |
| | | } |
| | | candidates.add(path); |
| | | lens.add(len); |
| | | tasksList.add(tasks); |
| | | congs.add(cong); |
| | | passOtherOutStationCounts.add(passOtherOutStationCount); |
| | | } |
| | | |
| | | if (candidates.isEmpty()) { |
| | | if (forceSkipPassOtherOutStation && skippedByPassOtherOutStation > 0) { |
| | | News.info("[WCS Debug] 所有站点路径候选均因经过其他出库站点被强制跳过"); |
| | | return new ArrayList<>(); |
| | | } |
| | | return allList.get(0); |
| | | } |
| | | |
| | | int minLen = Integer.MAX_VALUE; |
| | | int maxLen = Integer.MIN_VALUE; |
| | | double minCong = Double.MAX_VALUE; |
| | | double maxCong = -Double.MAX_VALUE; |
| | | int minPassOtherOutStationCount = Integer.MAX_VALUE; |
| | | int maxPassOtherOutStationCount = Integer.MIN_VALUE; |
| | | for (int i = 0; i < candidates.size(); i++) { |
| | | int l = lens.get(i); |
| | | double c = congs.get(i); |
| | | int p = passOtherOutStationCounts.get(i); |
| | | if (l < minLen) minLen = l; |
| | | if (l > maxLen) maxLen = l; |
| | | if (c < minCong) minCong = c; |
| | | if (c > maxCong) maxCong = c; |
| | | if (p < minPassOtherOutStationCount) minPassOtherOutStationCount = p; |
| | | if (p > maxPassOtherOutStationCount) maxPassOtherOutStationCount = p; |
| | | } |
| | | |
| | | double weightSum = lenWeightPercent + congWeightPercent + passOtherOutStationWeightPercent; |
| | | double lenW = weightSum <= 0 ? 0.5 : lenWeightPercent / weightSum; |
| | | double congW = weightSum <= 0 ? 0.5 : congWeightPercent / weightSum; |
| | | double passOtherOutStationW = weightSum <= 0 ? 0.0 : passOtherOutStationWeightPercent / weightSum; |
| | | |
| | | List<NavigateNode> best = null; |
| | | double bestCost = Double.MAX_VALUE; |
| | | int bestPassOtherOutStationCount = Integer.MAX_VALUE; |
| | | int bestTasks = Integer.MAX_VALUE; |
| | | int bestLen = Integer.MAX_VALUE; |
| | | for (int i = 0; i < candidates.size(); i++) { |
| | | int l = lens.get(i); |
| | | int t = tasksList.get(i); |
| | | double c = congs.get(i); |
| | | int p = passOtherOutStationCounts.get(i); |
| | | //归一化 |
| | | double lenNorm = (maxLen - minLen) <= 0 ? 0.0 : (l - minLen) / (double) (maxLen - minLen); |
| | | double congNorm = (maxCong - minCong) <= 0 ? 0.0 : (c - minCong) / (double) (maxCong - minCong); |
| | | double passOtherOutStationNorm = (maxPassOtherOutStationCount - minPassOtherOutStationCount) <= 0 |
| | | ? 0.0 |
| | | : (p - minPassOtherOutStationCount) / (double) (maxPassOtherOutStationCount - minPassOtherOutStationCount); |
| | | //获取权重 |
| | | double cost = lenNorm * lenW + congNorm * congW + passOtherOutStationNorm * passOtherOutStationW; |
| | | if (cost < bestCost |
| | | || (cost == bestCost && p < bestPassOtherOutStationCount) |
| | | || (cost == bestCost && p == bestPassOtherOutStationCount && t < bestTasks) |
| | | || (cost == bestCost && p == bestPassOtherOutStationCount && t == bestTasks && l < bestLen)) { |
| | | best = candidates.get(i); |
| | | bestCost = cost; |
| | | bestPassOtherOutStationCount = p; |
| | | bestTasks = t; |
| | | bestLen = l; |
| | | } |
| | | } |
| | | |
| | | if (best == null) { |
| | | return allList.get(0); |
| | | } |
| | | return best; |
| | | weights.lenWeightFactor = (lenWeightPercent / weightSum) * 2.0d; |
| | | weights.congWeightFactor = (congWeightPercent / weightSum) * 2.0d; |
| | | weights.passOtherOutStationPenaltyWeight = passOtherOutStationWeightPercent / 100.0d * 8.0d; |
| | | return weights; |
| | | } |
| | | |
| | | private Set<Integer> loadAllOutStationIdSet() { |
| | |
| | | return value.getInteger("stationId"); |
| | | } catch (Exception ignore) {} |
| | | return null; |
| | | } |
| | | |
| | | private double loadDoubleConfig(ConfigService configService, String code, double defaultValue) { |
| | | if (configService == null || code == null) { |
| | | return defaultValue; |
| | | } |
| | | Config config = configService.getOne(new QueryWrapper<Config>().eq("code", code)); |
| | | if (config == null || config.getValue() == null) { |
| | | return defaultValue; |
| | | } |
| | | String value = config.getValue().trim(); |
| | | if (value.endsWith("%")) { |
| | | value = value.substring(0, value.length() - 1); |
| | | } |
| | | try { |
| | | return Double.parseDouble(value); |
| | | } catch (Exception ignore) {} |
| | | return defaultValue; |
| | | } |
| | | |
| | | private boolean loadBooleanConfig(ConfigService configService, String code, boolean defaultValue) { |
| | | if (configService == null || code == null) { |
| | | return defaultValue; |
| | | } |
| | | Config config = configService.getOne(new QueryWrapper<Config>().eq("code", code)); |
| | | if (config == null || config.getValue() == null) { |
| | | return defaultValue; |
| | | } |
| | | String value = config.getValue().trim(); |
| | | if (value.isEmpty()) { |
| | | return defaultValue; |
| | | } |
| | | return "1".equals(value) |
| | | || "true".equalsIgnoreCase(value) |
| | | || "yes".equalsIgnoreCase(value) |
| | | || "y".equalsIgnoreCase(value) |
| | | || "on".equalsIgnoreCase(value); |
| | | } |
| | | |
| | | //判断当前节点到下一个节点是否为拐点 |