DispatherTest2.java 15.4 KB
package com.dituhui.pea.dispatch;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.UncheckedIOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.config.solver.SolverConfig;

import com.dituhui.pea.dispatch.pojo.Customer;
import com.dituhui.pea.dispatch.pojo.Depot;
import com.dituhui.pea.dispatch.pojo.Location;
import com.dituhui.pea.dispatch.pojo.Technician;
import com.dituhui.pea.dispatch.pojo.VehicleRoutingSolution;

/**
 * 真实数据测试
 * 
 * @author dell
 *
 */
public class DispatherTest2 {
	
	static long terminationSpentLimitSeconds = 1 * 60;

	public static void main(String[] args) throws UncheckedIOException, FileNotFoundException {
		System.out.println("hello");
		
		// 创建解决方案对象
		Map<Integer, String> customerIndexMap = loadCustomerIndex();
		Map<Integer, String> technicianIndexMap = loadTechnicianIndex();
		Map<String, Set<String>> technicianCodeSkillsMap = loadTechnicianCodeSkillsMap();
		Map<String, String> customerCodeSkillMap = loadCustomerCodeSkillMap();
		Map<String, Map<String, Long>> preferredlocationDistanceMap =loadPreferredlocationDistanceMap();
		Map<String, Integer> customerCodeServiceTimeMap = loadCustomerCodeServiceTimeMap();
		VehicleRoutingSolution problem = createVehicleRoutingSolution(customerIndexMap, technicianIndexMap,
				technicianCodeSkillsMap, customerCodeSkillMap, preferredlocationDistanceMap,customerCodeServiceTimeMap);

		// 创建求解器配置
		// 创建 SolverConfig 对象,并设置求解器配置
		SolverConfig solverConfig = new SolverConfig();
		solverConfig.setSolutionClass(VehicleRoutingSolution.class);
		solverConfig.withEntityClassList(Arrays.asList(Technician.class, Customer.class));// 这里不能漏掉,否则约束不生效
		solverConfig.withTerminationSpentLimit(Duration.ofSeconds(terminationSpentLimitSeconds));

		// 约束条件
		solverConfig.withConstraintProviderClass(com.dituhui.pea.dispatch.constraint.VehicleRoutingConstraintProvider.class);

		// 创建求解器
		SolverFactory<VehicleRoutingSolution> solverFactory = SolverFactory.create(solverConfig);
		Solver<VehicleRoutingSolution> solver = solverFactory.buildSolver();
		VehicleRoutingSolution solution = solver.solve(problem);

		printSolution(solution, customerIndexMap, technicianIndexMap);

		System.out.println("hardScore: " + solution.getScore().hardScore());
		System.out.println("softScore: " + solution.getScore().softScore());

	}

	private static Map<String, Integer> loadCustomerCodeServiceTimeMap()
			throws UncheckedIOException, FileNotFoundException {
		List<String> customerServiceTime = IOUtils.readLines(new FileInputStream("data/customerServiceTime.csv"), "utf-8");
		Map<String, Integer> customerCodeServiceTimeMap = new HashMap<String, Integer>();// code-time
		for (int i = 0; i < customerServiceTime.size(); i++) {
			String line = customerServiceTime.get(i);
			String[] temps = line.split(",");
			customerCodeServiceTimeMap.put(temps[0], Integer.parseInt(temps[1]));
		}
		return customerCodeServiceTimeMap;
	}

	private static Map<String, Map<String, Long>> loadPreferredlocationDistanceMap()
			throws UncheckedIOException, FileNotFoundException {
		List<String> technicianCodeLocation = IOUtils
				.readLines(new FileInputStream("data/technicianLocation.csv"), "utf-8");
		Map<String, String> technicianCodeLocationMap = new HashMap<String, String>();// 序号-code
		for (int i = 0; i < technicianCodeLocation.size(); i++) {
			String line = technicianCodeLocation.get(i);
			String[] temps = line.split(",");
			technicianCodeLocationMap.put(temps[0], temps[1] + "," + temps[2]);
		}

		List<String> customerCodeLocation = IOUtils
				.readLines(new FileInputStream("data/customerLocation.csv"), "utf-8");
		Map<String, String> customerCodeLocationMap = new HashMap<String, String>();// 序号-code
		for (int i = 0; i < customerCodeLocation.size(); i++) {
			String line = customerCodeLocation.get(i);
			String[] temps = line.split(",");
			customerCodeLocationMap.put(temps[0], temps[1] + "," + temps[2]);
		}

		// 生成订单和技术员的偏好距离Map 技术员-订单-距离
		Map<String, Map<String, Long>> customerTecnicianDistanceMap = new HashMap<String, Map<String, Long>>();
		customerCodeLocationMap.forEach((customerCode, value) -> {
			technicianCodeLocationMap.forEach((technicianCode, value2) -> {
				String[] temps = RegExUtils.removeAll(value, "\"").split(",");
				String[] temps2 = RegExUtils.removeAll(value2, "\"").split(",");
				long distance = (long) getDistance(Double.parseDouble(temps[1]), Double.parseDouble(temps[0]),
						Double.parseDouble(temps2[1]), Double.parseDouble(temps2[0]));
				Map<String, Long> customerMaps = customerTecnicianDistanceMap.get(technicianCode);
				if(null == customerMaps) {
					customerMaps = new HashMap<String, Long>();
					customerTecnicianDistanceMap.put(technicianCode, customerMaps);
				}
				customerMaps.put(customerCode, distance);
			});
		});
		return customerTecnicianDistanceMap;
	}

	/**
	 * 获取经纬度距离
	 * 
	 * @param lat1 y
	 * @param lon1 x
	 * @param lat2 y
	 * @param lon2 x
	 * @return
	 */
	public static double getDistance(double lat1, double lon1, double lat2, double lon2) {
		double diffLongitudes = Math.toRadians(Math.abs(lon1 - lon2));
		double diffLatitudes = Math.toRadians(Math.abs(lat1 - lat2));
		double slat = Math.toRadians(lat1);
		double flat = Math.toRadians(lat2);
		// haversine formula
		double a = Math.sin(diffLatitudes / 2) * Math.sin(diffLatitudes / 2)
				+ Math.cos(slat) * Math.cos(flat) * Math.sin(diffLongitudes / 2) * Math.sin(diffLongitudes / 2);
		double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); // angular distance in radians
		return 6378137 * c;
	}

	private static Map<String, String> loadCustomerCodeSkillMap() throws UncheckedIOException, FileNotFoundException {
		List<String> customerSkill = IOUtils.readLines(new FileInputStream("data/customerSkill.csv"), "utf-8");
		Map<String, String> customerCodeSkillMap = new HashMap<String, String>();// code-技能
		for (int i = 0; i < customerSkill.size(); i++) {
			String line = customerSkill.get(i);
			String[] temps = line.split(",");
			customerCodeSkillMap.put(temps[0], temps[1]);
		}
		return customerCodeSkillMap;
	}

	private static Map<String, Set<String>> loadTechnicianCodeSkillsMap()
			throws UncheckedIOException, FileNotFoundException {
		List<String> technicianSkills = IOUtils.readLines(new FileInputStream("data/technicianSkills.csv"), "utf-8");
		Map<String, Set<String>> technicianCodeSkillsMap = new HashMap<String, Set<String>>();// code-技能
		for (int i = 0; i < technicianSkills.size(); i++) {
			String line = technicianSkills.get(i);
			String[] temps = line.split(",");

			String code = temps[0];
			Set<String> skills = technicianCodeSkillsMap.get(code);
			if (null == skills) {
				skills = new HashSet<>();
				technicianCodeSkillsMap.put(code, skills);
			}
			skills.add(temps[1]);
		}
		return technicianCodeSkillsMap;
	}

	private static Map<Integer, String> loadTechnicianIndex() throws UncheckedIOException, FileNotFoundException {
		List<String> technicianIndexlines = IOUtils.readLines(new FileInputStream("data/technicianIndex.csv"), "utf-8");
		Map<Integer, String> technicianIndexMap = new HashMap<Integer, String>();// 序号-code
		for (int i = 0; i < technicianIndexlines.size(); i++) {
			technicianIndexMap.put(i + 1, technicianIndexlines.get(i));
		}
		return technicianIndexMap;
	}

	private static Map<Integer, String> loadCustomerIndex() throws UncheckedIOException, FileNotFoundException {
		List<String> customerIndexlines = IOUtils.readLines(new FileInputStream("data/customerIndex.csv"), "utf-8");
		Map<Integer, String> customerIndexMap = new HashMap<Integer, String>();// 序号-code
		for (int i = 0; i < customerIndexlines.size(); i++) {
			customerIndexMap.put(i + 1, customerIndexlines.get(i));
		}
		return customerIndexMap;
	}

	private static VehicleRoutingSolution createVehicleRoutingSolution(Map<Integer, String> customerIndexMap,
			Map<Integer, String> technicianIndexMap, Map<String, Set<String>> technicianCodeSkillsMap,
			Map<String, String> customerCodeSkillMap, Map<String, Map<String, Long>> preferredlocationDistanceMap, Map<String, Integer> customerCodeServiceTimeMap) throws UncheckedIOException, FileNotFoundException {
		VehicleRoutingSolution vehicleRoutingSolution = new VehicleRoutingSolution();

		// 翻转map
		Map<String, Integer> customerIndexMap2 = customerIndexMap.entrySet().stream()
				.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
		// 初始化距离矩阵
		List<String> pathMatrixlines = IOUtils.readLines(new FileInputStream("data/pathMatrix.csv"), "utf-8");
		long[][] pathMatrix = new long[customerIndexMap.keySet().size() + 1][customerIndexMap.keySet().size() + 1];
		for (int i = 0; i < pathMatrixlines.size(); i++) {
			String line = pathMatrixlines.get(i);
			String[] temps = line.split(",");
			for (int j = 0; j < temps.length; j++) {
				pathMatrix[i][j] = (long) (Float.parseFloat(temps[j]) * 1000);
			}
		}
		Map<Integer, Location> locationIndex = new HashMap<Integer, Location>();
		for (int i = 0; i < pathMatrix.length; i++) {
			// 1-6
			locationIndex.put(i + 1, new Location(i + 1));
		}
		for (int i = 0; i < pathMatrix.length; i++) {
			Location locationi = locationIndex.get(i + 1);
			for (int j = 0; j < pathMatrix[i].length; j++) {
				Location locationj = locationIndex.get(j + 1);
				locationi.getDistanceMap().put(locationj, pathMatrix[i][j]);
			}
		}

		// 初始化时间矩阵

		// 初始化订单服务窗
		List<String> customerWindowslines = IOUtils.readLines(new FileInputStream("data/customerWindows.csv"), "utf-8");
		Map<Integer, Integer> customerStartMap = new HashMap<Integer, Integer>();
		Map<Integer, Integer> customerEndMap = new HashMap<Integer, Integer>();
		for (int i = 0; i < customerWindowslines.size(); i++) {
			String line = customerWindowslines.get(i);
			String[] temps = line.split(",");
			customerStartMap.put(customerIndexMap2.get(temps[0]), 480 + Integer.parseInt(temps[1]));
			customerEndMap.put(customerIndexMap2.get(temps[0]), 480 + Integer.parseInt(temps[2]));
		}

		// 初始化订单需要技能
		Map<Integer, String> customerSkillMap = new HashMap<Integer, String>();
		for (int i = 0; i < customerWindowslines.size(); i++) {
			// 获取订单技能
			customerSkillMap.put(i + 1, customerCodeSkillMap.get(customerIndexMap.get(i + 1)));

			if (null == customerCodeSkillMap.get(customerIndexMap.get(i + 1))) {
				System.err.printf("%s code:%s 没有技能 %n", i + 1, customerIndexMap.get(i + 1));
				System.exit(0);
			}

		}

		// 初始化订单+技能服务时间
		List<Customer> customerList = new ArrayList<>();
		for (int i = 0; i < customerIndexMap.keySet().size(); i++) {
			customerList.add(new Customer(i + 1, customerIndexMap.get(i + 1), locationIndex.get(i + 2),
					customerStartMap.get(i + 1), customerEndMap.get(i + 1), customerSkillMap.get(i + 1),
					// 初始化技能服务时间
					customerCodeServiceTimeMap.get(customerIndexMap.get(i + 1))));
		}

		// 初始化Depot
		Depot depot = new Depot(1, locationIndex.get(1), 480, 1080);

		// 初始化技术员
		List<Technician> technicianList = new ArrayList<>();
		for (int i = 0; i < technicianIndexMap.keySet().size(); i++) {
			// 获取第i+1个技术员的技能set
			Set<String> skills = technicianCodeSkillsMap.get(technicianIndexMap.get(i + 1));

			if (null == skills || skills.size() == 0) {
				System.err.printf("技术员%s code:%s 没有技能 %n", i + 1, technicianIndexMap.get(i + 1));
				System.exit(0);
			}

			technicianList.add(new Technician(i + 1, technicianIndexMap.get(i + 1), depot, 480, 1080, skills,
					preferredlocationDistanceMap.get(technicianIndexMap.get(i + 1))));
		}

		vehicleRoutingSolution.setCustomerList(customerList);
		vehicleRoutingSolution.setDepot(depot);
		vehicleRoutingSolution.setLocationList(new ArrayList<>(locationIndex.values()));
		vehicleRoutingSolution.setTechnicianList(technicianList);

		return vehicleRoutingSolution;
	}

	static void printSolution(VehicleRoutingSolution solution, Map<Integer, String> customerIndexMap,
			Map<Integer, String> technicianIndexMap) {
		System.out.println("技能约束:");
		solution.getTechnicianList().forEach(technician -> {
			System.out.printf("技术员%s(%s) %s%n", technician.getId(), technicianIndexMap.get((int) technician.getId()),
					technician.getSkills());
			for (Customer customer : technician.getCustomerList()) {
				if (!technician.getSkills().contains(customer.getRequiredSkill())) {
					// no match
					System.err.printf("	预约单%s(%s) %s%n", customer.getId(), customerIndexMap.get((int) customer.getId()),
							customer.getRequiredSkill());
				} else {
					System.out.printf("	预约单%s(%s) %s%n", customer.getId(), customerIndexMap.get((int) customer.getId()),
							customer.getRequiredSkill());
				}
			}
		});

		AtomicInteger totalNum = new AtomicInteger(0);
		solution.getTechnicianList().forEach(technician -> {
			System.out.printf("技术员%s(%s) [%s,%s]%n", technician.getId(),
					technicianIndexMap.get((int) technician.getId()), printTime(technician.getStartTime()),
					printTime(technician.getEndTime()));
			totalNum.addAndGet(technician.getCustomerList().size());
			for (Customer customer : technician.getCustomerList()) {
				Customer previousCustomer = customer.getPreviousCustomer();
				int startPath, endPath;//路上时间
				if (null == previousCustomer) {
					startPath = technician.getDepot().getStartTime();
					// endPath = startPath + customer.getLocation().getPathTimeTo(technician.getDepot().getLocation());
					endPath = startPath + technician.getDepot().getLocation().getPathTimeTo(customer.getLocation());
				} else {
					startPath = previousCustomer.getDepartureTime();
					// endPath = startPath + customer.getLocation().getPathTimeTo(previousCustomer.getLocation());
					endPath = startPath + previousCustomer.getLocation().getPathTimeTo(customer.getLocation());
				}

				System.out.printf("	预约单%s(%s) 预约时间窗[%s=>%s] 路上时间[%s=>%s] 早到等待时间[%s=>%s] 派工时间[%s=>%s] 迟到时间[%s=>%s]%n",
						customer.getId(), customerIndexMap.get((int) customer.getId()),
						// 预约时间窗
						printTime(customer.getStartTime()), printTime(customer.getEndTime()),
						// 路上时间
						printTime(startPath), printTime(endPath),
						// 早到等待时间
						customer.getArrivalTime() < customer.getStartTime() ? printTime(endPath) : "",
						customer.getArrivalTime() < customer.getStartTime() ? printTime(customer.getStartTime()) : "",
						// 派工时间
						printTime(customer.getArrivalTime()), printTime(customer.getDepartureTime()),
						// 迟到时间
						customer.getArrivalTime() > customer.getEndTime() ? printTime(customer.getEndTime()) : "",
						customer.getArrivalTime() > customer.getEndTime() ? printTime(customer.getArrivalTime()) : "");
			}
		});
	}

	private static String printTime(int startTime) {
		int hour = startTime / 60;
		int minite = startTime % 60;
		return StringUtils.leftPad("" + hour, 2, '0') + ":" + StringUtils.leftPad("" + minite, 2, '0');
	}

}