Arrival2Schedule.java 11 KB
package com.bsth.data.match;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import com.bsth.data.arrival.ArrivalComparator;
import com.bsth.data.arrival.ArrivalData_GPS;
import com.bsth.data.arrival.ArrivalEntity;
import com.bsth.data.schedule.DayOfSchedule;
import com.bsth.data.schedule.ScheduleComparator;
import com.bsth.entity.realcontrol.ScheduleRealInfo;
import com.bsth.service.directive.DirectiveService;
import com.bsth.websocket.handler.SendUtils;

/**
 * 
 * @ClassName: Arrival2Schedule 
 * @Description: TODO(到离站数据 和 计划班次进行匹配  注意线程安全!!!!) 
 * @author PanZhao 
 * @date 2016年8月28日 上午1:13:02 
 *
 */
@Component
public class Arrival2Schedule implements ApplicationContextAware {
	
	private static DayOfSchedule dayOfSchedule;
	private static SendUtils sendUtils;
	private static DirectiveService directiveService;
	private final static int ONE_MINUTE = 1000 * 60;
	//定一个4小时的范围,基本能对正常班次进行容错。主要防止早上停车场GPS飘导致完成晚上的进场班次
	private final static int FOUR_HOURS = 1000 * 60 * 60 * 4;
	
	private static Logger logger = LoggerFactory.getLogger(Arrival2Schedule.class);
	
	private static Map<String, ExpectArrivalEnd> expectMap = new HashMap<>();
	
	/**
	 * 
	 * @Title: start 
	 * @Description: TODO(开始) 
	 * @param @param cars    需要匹配的车辆集合
	 */
	public static void start(Set<String> cars){
		
		for(String car : cars){
			new SchMatchThread(car).start();
		}
	}
	
	public static class SchMatchThread extends Thread{
		String nbbm;
		public SchMatchThread(String nbbm){
			this.nbbm = nbbm;
		}
		
		//排序器
		private ScheduleComparator.FCSJ schComparator = new ScheduleComparator.FCSJ();
		private ArrivalComparator arrComparator = new ArrivalComparator();
		private MatchResultComparator mrComparator = new MatchResultComparator();
		private SimpleDateFormat sdfyyyyMMddHHmm = new SimpleDateFormat("yyyy-MM-ddHH:mm");
		
		@Override
		public void run() {
			//班次列表
			List<ScheduleRealInfo> schList = dayOfSchedule.findByNbbm(nbbm);
			//进出起终点数据
			List<ArrivalEntity> arrList = ArrivalData_GPS.findByNbbm(nbbm);
			
			if(schList.size() == 0 || arrList.size() == 0)
				return;
			
			//排序
			Collections.sort(schList, schComparator);
			Collections.sort(arrList, arrComparator);
			//过滤班次
			schList = matchFilter(schList);
			
			//检查并修正首班出场终点信号,可能会出现走向异常
			correctFirstSignal(schList, arrList);
			
			
			//用实际来匹配计划
			for(ArrivalEntity arr : arrList){
				match(arr, schList);
			}
		}
		
		private void match(ArrivalEntity arr, List<ScheduleRealInfo> schList) {
			
			if(arr.getInOut() == 1)
				matchOut(arr, schList);
			else if(arr.getInOut() == 0)
				matchIn(arr, schList);
		}
		
		private List<ScheduleRealInfo> matchFilter(List<ScheduleRealInfo> schList) {
			List<ScheduleRealInfo> list = new ArrayList<>();
			for(ScheduleRealInfo sch : schList){
				//烂班不匹配
				if(sch.isDestroy())
					continue;
				
				//没有里程的不匹配
				if(sch.getBcsj() == null && sch.getJhlc() == null)
					continue;
				
				list.add(sch);
			}
			return list;
		}
		
		private void matchOut(ArrivalEntity arr, List<ScheduleRealInfo> schList){
			if(arr.getFlag() == -1)
				return;
			
			List<MatchResult> mrs = new ArrayList<>();
			ScheduleRealInfo sch;
			MatchResult mr;
			for(int i = 0; i < schList.size(); i ++){
				sch = schList.get(i);
				if(!arr.isTcc() && arr.getUpDown() != Integer.parseInt(sch.getXlDir()))
					continue;
				
				if(!arr.getStopNo().equals(sch.getQdzCode()))
					continue;
				
				//班次有实发时间
				if(sch.getFcsjActualTime() != null){
					//实际发车已经被引用
					if(Math.abs(arr.getTs() - sch.getFcsjActualTime()) < ONE_MINUTE)
						return;
					else
						continue;
				}
				//添加一个匹配结果
				mr = new MatchResult();
				mr.sch = sch;
				mr.ts = arr.getTs();
				mr.diff = arr.getTs() - sch.getFcsjT();
				mr.success = dayOfSchedule.validStartTime(sch, arr.getTs());
				
				if(Math.abs(mr.diff) < FOUR_HOURS && mr.success)
					mrs.add(mr);
			}
			
			if(mrs.size() > 0){
				//排序后的第一个 就是最合适的匹配
				Collections.sort(mrs, mrComparator);
				mr = mrs.get(0);
				
				//漂移判定
				if(driftCheck(mr, arr)){
					carOut(mr);
				}
			}
		}
		
		private void matchIn(ArrivalEntity inArr, List<ScheduleRealInfo> schList){
			
			List<MatchResult> mrs = new ArrayList<>();
			ScheduleRealInfo sch;
			MatchResult mr;
			for(int i = 0; i < schList.size(); i ++){
				sch = schList.get(i);
				if(!inArr.isTcc() && inArr.getUpDown() != Integer.parseInt(sch.getXlDir()))
					continue;
				
				if(!inArr.getStopNo().equals(sch.getZdzCode()))
					continue;
				
				//班次有实达时间
				if(sch.getZdsjActualTime() != null){
					//实际到达已经被引用
					if(Math.abs(inArr.getTs() - sch.getZdsjActualTime()) < ONE_MINUTE)
						return;
					else
						continue;
				}
				
				//添加一个匹配结果
				mr = new MatchResult();
				mr.sch = sch;
				mr.ts = inArr.getTs();
				mr.diff = inArr.getTs() - sch.getZdsjT();
				mr.success = dayOfSchedule.validEndTime(sch, inArr.getTs());
				if(Math.abs(mr.diff) < FOUR_HOURS && mr.success)
					mrs.add(mr);
			}
			
			if(mrs.size() > 0){
				//排序后的第一个 就是最合适的匹配
				Collections.sort(mrs, mrComparator);
				mr = mrs.get(0);
				carInStop(mr);
			}
		}
		
		/**
		 * 
		 * @Title: carOut 
		 * @Description: TODO(车辆发出) 
		 */
		public void carOut(MatchResult mr){
			ScheduleRealInfo sch = mr.sch;
			
			if(expectMap.containsKey(nbbm)){
				ExpectArrivalEnd ead = expectMap.get(nbbm);
				if(mr.ts < ead.getEndTime())
					return;
				else
					expectMap.remove(nbbm);
			}
			//设置发车时间
			sch.setFcsjActualAll(mr.ts);
			//通知客户端
			sendUtils.sendFcsj(sch);
			//持久化
			dayOfSchedule.save(sch);
			//车辆当前执行班次
			dayOfSchedule.addExecPlan(sch);
			//期望车辆到达的终点
			ExpectArrivalEnd ead = new ExpectArrivalEnd();
			ead.setNbbm(sch.getClZbh());
			ead.setEndStation(sch.getZdzCode());
			ead.setEndTime(sch.getZdsjT());
			expectMap.put(ead.getNbbm(), ead);
		}
		
		/**
		 * 
		 * @Title: carInStop 
		 * @Description: TODO(车辆进入终点站) 
		 */
		public void carInStop(MatchResult mr){
			ScheduleRealInfo sch = mr.sch;
			String nbbm=sch.getClZbh();
			if(expectMap.containsKey(nbbm)){
				ExpectArrivalEnd ead = expectMap.get(nbbm);
				if(mr.ts < ead.getEndTime() 
						&& !mr.sch.getZdzCode().equals(ead.getEndStation())){
					return;
				}
				else
					expectMap.remove(nbbm);
			}
			sch.setZdsjActualAll(mr.ts);
			
			int doneSum = dayOfSchedule.doneSum(nbbm);
			ScheduleRealInfo next = dayOfSchedule.next(sch);
			if(null != next){
				next.setQdzArrDateSJ(sch.getZdsjActual());
				//下发调度指令
				directiveService.send60Dispatch(next, doneSum, "到站@系统");
				
				//完成“起点既停车场”的进场班次
				if(next.getBcType().equals("in") && next.getJhlc() == null)
					next.setFcsjActualAll(mr.ts);
				
				//套跑  -下发线路切换指令
				if(!next.getXlBm().equals(sch.getXlBm()))
					directiveService.lineChange(nbbm, next.getXlBm(), "套跑@系统");
			}
			else//下发文本指令(已结束运营)
				directiveService.send60Phrase(nbbm, "到达终点 " + sch.getZdzName() + ",已完成当日所有排班。", "系统");
			//通知客户端
			sendUtils.sendZdsj(sch, next, doneSum);
			//持久化
			dayOfSchedule.save(sch);
			logger.info(sch.getClZbh() + "移除正在执行班次," + sch.getFcsj());
			//移除车辆正在执行班次索引
			dayOfSchedule.removeExecPlan(nbbm);
		}
		
		/**
		 * 
		 * @Title: correctFirstSignal 
		 * @Description: TODO(检查并纠正首班出场到离站) 
		 */
		private final static long TEN_MINUTES = 1000 * 60 * 10;
		private void correctFirstSignal(List<ScheduleRealInfo> schList, List<ArrivalEntity> arrList) {
			ScheduleRealInfo sch = schList.get(0);
			ArrivalEntity arr = arrList.get(0);
			
			//有里程的出场班次才需要纠正
			if(arr.isCorrect() || !sch.getBcType().equals("out") || sch.getJhlc() == null || sch.getBcsj() == null)
				return;
			
			//如果首个进出站信号是出场
			if(arr.isOutTcc() && arrList.size() >= 2)
				arr = arrList.get(1);
			else
				return;
			
			//出场任务 “进终点” 信号才需要纠正
			if(arr.getInOut() != 0)
				return;
			
			//在计划终点之前到达,或者之后10分钟内到达
			if(arr.getTs() < sch.getZdsjT()
					|| arr.getTs() - sch.getZdsjT() < TEN_MINUTES){
				
				int upDown = Integer.parseInt(sch.getXlDir());
				//走向不一致,相信班次的走向。纠正进站信号
				if(arr.getUpDown() != upDown
						&& arr.getStopName().equals(sch.getZdzName())){
					
					
					String old = arr.toString();
					arr.setUpDown(upDown);
					arr.setStopNo(sch.getZdzCode());
					arr.setCorrect(true);
					arr.setCorrectText(old + " | " + arr.toString());
					
					logger.info("被纠正的信号:" + arr.getCorrectText());
				}
			}
		}
		
		/**
		 * 
		 * @Title: driftCheck 
		 * @Description: TODO(漂移判定) 
		 */
		public boolean driftCheck(MatchResult mr, ArrivalEntity arr){
			try{
				//上行发车,和到达时间比较一下。起点一般不会立即发出
				if(mr.sch.getXlDir().equals("0") 
						&& null != mr.sch.getQdzArrDateSJ()){
					
					long dt = sdfyyyyMMddHHmm.parse(mr.sch.getRealExecDate() + mr.sch.getQdzArrDateSJ()).getTime();
					
					//停站时间少于 计划的3分之1,标记为漂移信号
					if((mr.ts - dt < (mr.sch.getDfsjT() - dt) / 3)){
						arr.setCorrect(true);
						arr.setCorrectText("停站时间太短,标记为信号漂移");
						arr.setFlag(-1);
						
						logger.info("漂移判定:" + arr);
						return false;
					}
				}
			}catch(Exception e){
				logger.error("", e);
			}
			return true;
		}
	}
	
	@Override
	public void setApplicationContext(ApplicationContext arg0) throws BeansException {
		sendUtils = arg0.getBean(SendUtils.class);
		dayOfSchedule = arg0.getBean(DayOfSchedule.class);
		directiveService = arg0.getBean(DirectiveService.class);
	}
	
	/**
	 * 
	 * @Title: removeExpect 
	 * @Description: TODO(清除预期站点) 
	 * @param @param nbbm   
	 */
	public void removeExpect(String nbbm){
		expectMap.remove(nbbm);
	}
}