Commit 8c25ca0b by chamberone

feat: 代码重构,添加评分等工具

1 parent 430eddf7
package com.dituhui.pea.dispatch.constraint;
/**
* 约束枚举
*
* @author zhangguoping
*
*/
public enum ConstraintNameEnum {
// 硬约束
/**
* 技术员技能跟订单相匹配
*/
skillMatch,
/**
* 技术员到达时间跟时间窗吻合
*/
customerTimeWindowsMatch,
// 软约束
/**
* 订单数量均衡
*/
technicianBalanceSoft,
/**
* 总路程最小
*/
totalDistance,
/**
* 技术员中心点偏好
*/
preferredTotalDistance
}
......@@ -12,38 +12,53 @@ public class DispatchConstraintProvider implements ConstraintProvider {
@Override
public Constraint[] defineConstraints(ConstraintFactory factory) {
return new Constraint[] { greaterThanZero(factory), customerTimeWindowsMatch1(factory),
customerTimeWindowsMatch2(factory), skillMatch(factory), technicianBalance(factory),
technicianBalance2(factory), technicianBalanceSoft(factory), totalDistance(factory),
preferredTotalDistance(factory) };
return new Constraint[] {
// 硬约束
// 运行时长提升效果
// 5s/60s: 技能权重4-技能匹配问题0个,时间窗问题14个
// 10s/300s: 技能权重4-技能匹配问题0个,时间窗问题9个
customerTimeWindowsMatch(factory),
skillMatch(factory),
// 软约束
technicianBalanceSoft(factory),
totalDistance(factory),
preferredTotalDistance(factory)
};
}
// ************************************************************************
// Hard constraints
// ************************************************************************
// 1,每个技术员至少分配一个greaterThanZero
// 2,技术员到达时间跟时间窗吻合customerTimeWindowsMatch
// 3,技术员时间窗吻合technicianTimeWindowsMatch
// 4,技术员技能跟订单相匹配skillMatch
// 5,订单均分technicianBalance
// 1,技术员到达时间跟时间窗吻合customerTimeWindowsMatch
// 2,技术员时间窗吻合technicianTimeWindowsMatch
// 3,技术员技能跟订单相匹配skillMatch
public Constraint greaterThanZero(ConstraintFactory factory) {
return factory.forEach(Technician.class).filter(technician -> technician.getCustomerList().size() == 0)
.penalizeLong(HardSoftLongScore.ONE_HARD, technician -> 1).asConstraint("每个技术员至少分配一个单子");
}
protected Constraint customerTimeWindowsMatch1(ConstraintFactory factory) {
protected Constraint customerTimeWindowsMatch(ConstraintFactory factory) {
return factory.forEach(Customer.class).filter(
customer -> customer.getTechnician() != null && customer.getArrivalTime() > customer.getEndTime())
.penalizeLong(HardSoftLongScore.ONE_HARD, customer -> 1).asConstraint("技术员到达时间跟时间窗吻合1");
.penalizeLong(HardSoftLongScore.ONE_HARD,
// 迟到每2小时扣一分
customer -> (long) Math.ceil((customer.getArrivalTime() - customer.getEndTime()) / 120F))
.asConstraint(ConstraintNameEnum.customerTimeWindowsMatch.name());
}
protected Constraint customerTimeWindowsMatch2(ConstraintFactory factory) {
// 迟到2小时惩罚
return factory.forEach(Customer.class).filter(
customer -> customer.getTechnician() != null && customer.getArrivalTime() > customer.getEndTime() + 120)
.penalizeLong(HardSoftLongScore.ONE_HARD, customer -> 1).asConstraint("技术员到达时间跟时间窗吻合2");
}
// protected Constraint customerTimeWindowsMatch1(ConstraintFactory factory) {
// return factory.forEach(Customer.class).filter(
// customer -> customer.getTechnician() != null && customer.getArrivalTime() > customer.getEndTime())
// .penalizeLong(HardSoftLongScore.ONE_HARD, customer -> 1).asConstraint("技术员到达时间跟时间窗吻合1");
// }
//
// protected Constraint customerTimeWindowsMatch2(ConstraintFactory factory) {
// // 迟到2小时惩罚
// return factory.forEach(Customer.class).filter(
// customer -> customer.getTechnician() != null && customer.getArrivalTime() > customer.getEndTime() + 120)
// .penalizeLong(HardSoftLongScore.ONE_HARD, customer -> 1).asConstraint("技术员到达时间跟时间窗吻合2");
// }
protected Constraint skillMatch(ConstraintFactory factory) {
return factory.forEach(Customer.class)
......@@ -54,38 +69,21 @@ public class DispatchConstraintProvider implements ConstraintProvider {
// 3-技能匹配问题1个,时间窗问题8个
// 4-技能匹配问题0个,时间窗问题14个
customer -> 4)
.asConstraint("技术员技能跟订单相匹配skillMatch");
}
protected Constraint technicianBalance(ConstraintFactory factory) {
// 会导致剩余单子集中?
return factory.forEach(Technician.class).filter(technician -> technician.getOffWorkTime() > 1140)
.penalizeLong(HardSoftLongScore.ONE_HARD,
technician -> (long) Math.ceil(technician.getOffWorkTime() / 1440f))
.asConstraint("订单均分");
}
protected Constraint technicianBalance2(ConstraintFactory factory) {
// 单量不能过少 FIXME
return factory.forEach(Technician.class).filter(technician -> technician.getOffWorkTime() <= 960)
.penalizeLong(HardSoftLongScore.ONE_HARD, technician -> 1).asConstraint("订单均分2");
.asConstraint(ConstraintNameEnum.skillMatch.name());
}
// protected Constraint technicianBalance(ConstraintFactory factory) {
// return factory.forEachUniquePair(Technician.class, Joiners.filtering(
// // 一个人的工作时长超过另外一个人的2倍 FIXME 无效
// (t1, t2) -> (t1.getWorkTime() > t2.getWorkTime() * 2) || (t2.getWorkTime() > t1.getWorkTime() * 2)))
// .penalizeLong(HardSoftLongScore.ONE_HARD, (c1, c2) -> 1).asConstraint("订单均分");
// // 会导致剩余单子集中?
// return factory.forEach(Technician.class).filter(technician -> technician.getOffWorkTime() > 1140)
// .penalizeLong(HardSoftLongScore.ONE_HARD,
// technician -> (long) Math.ceil(technician.getOffWorkTime() / 1440f))
// .asConstraint("订单均分");
// }
// protected Constraint technicianBalance(ConstraintFactory factory) {
// return factory.forEachUniquePair(Technician.class, Joiners.filtering(
// // 一个人的数量是另外一个人的3倍 FIXME 无效
// (t1, t2) -> {
// int size1 = t1.getCustomerSize();
// int size2 = t2.getCustomerSize();
// return (size1 > size2 * 3) || (size2 > size1 * 3);
// })).penalizeLong(HardSoftLongScore.ONE_HARD, (c1, c2) -> 1).asConstraint("订单均分");
//
// protected Constraint technicianBalance2(ConstraintFactory factory) {
// // 单量不能过少 FIXME
// return factory.forEach(Technician.class).filter(technician -> technician.getOffWorkTime() <= 960)
// .penalizeLong(HardSoftLongScore.ONE_HARD, technician -> 1).asConstraint("订单均分2");
// }
// ************************************************************************
......@@ -97,19 +95,21 @@ public class DispatchConstraintProvider implements ConstraintProvider {
protected Constraint totalDistance(ConstraintFactory factory) {
return factory.forEach(Technician.class)
.penalizeLong(HardSoftLongScore.ONE_SOFT, Technician::getTotalDistanceMeters).asConstraint("总路程最小");
.penalizeLong(HardSoftLongScore.ONE_SOFT, Technician::getTotalDistanceMeters)
.asConstraint(ConstraintNameEnum.totalDistance.name());
}
protected Constraint preferredTotalDistance(ConstraintFactory factory) {
return factory.forEach(Technician.class)
.penalizeLong(HardSoftLongScore.ONE_SOFT, Technician::getPreferredTotalDistanceMeters)
.asConstraint("技术员中心点偏好");
.asConstraint(ConstraintNameEnum.preferredTotalDistance.name());
}
protected Constraint technicianBalanceSoft(ConstraintFactory factory) {
return factory.forEachUniquePair(Technician.class).penalizeLong(HardSoftLongScore.ONE_SOFT,
// 权重需要调节,差距一个相当于多一公里 FIXME 这里应该是时长均衡,不是订单量均衡
(a, b) -> Math.abs(a.getCustomerSize() - b.getCustomerSize()) * 1000).asConstraint("订单均分soft");
(a, b) -> Math.abs(a.getCustomerSize() - b.getCustomerSize()) * 1000)
.asConstraint(ConstraintNameEnum.technicianBalanceSoft.name());
}
}
package com.dituhui.pea.dispatch.eventListener;
import org.optaplanner.core.api.solver.event.BestSolutionChangedEvent;
import com.dituhui.pea.dispatch.pojo.DispatchSolution;
public class DispatchSolverEventListener implements org.optaplanner.core.api.solver.event.SolverEventListener<DispatchSolution> {
@Override
public void bestSolutionChanged(BestSolutionChangedEvent<DispatchSolution> event) {
// TODO Auto-generated method stub
}
}
......@@ -13,6 +13,11 @@ import lombok.Setter;
public class Location {
private final long id;
// 坐标点
private float x;
private float y;
@JsonIgnore
private Map<Location, Long> distanceMap= new HashMap<Location, Long>();// 路网距离矩阵
@JsonIgnore
......@@ -21,6 +26,12 @@ public class Location {
public Location(long id) {
this.id = id;
}
public Location(long id, float x, float y) {
this.id = id;
this.x = x;
this.y = y;
}
/**
* Set the distance map. Distances are in meters.
......
......@@ -22,31 +22,31 @@ import lombok.Setter;
public class Technician {
@PlanningId
private long id;
private long id;
private String code;
@JsonIgnore
private Depot depot;
// 上班时间窗 分钟480-1080 8-18点
private int startTime;
private int endTime;
@JsonIgnore
private Depot depot;
// 上班时间窗 分钟480-1080 8-18点
private int startTime;
private int endTime;
// 技能
private Set<String> skills;
// 偏好坐标
// private Location preferredlocation;
// teck code : customer code , distance
@JsonIgnore
private Location preferredlocation;
// technician code : customer code , distance
@JsonIgnore
private Map<String, Long> preferredlocationDistanceMap = new HashMap<String, Long>();
@PlanningListVariable
private List<Customer> customerList = new ArrayList<>();
@PlanningListVariable
private List<Customer> customerList = new ArrayList<>();
public Technician() {
}
public Technician() {
}
public Technician(long id, String code, Depot depot, int startTime, int endTime, Set<String> skills,
Map<String, Long> preferredlocationDistanceMap) {
Map<String, Long> preferredlocationDistanceMap, Location preferredlocation) {
this.id = id;
this.code = code;
this.depot = depot;
......@@ -54,53 +54,54 @@ public class Technician {
this.endTime = endTime;
this.skills = skills;
this.preferredlocationDistanceMap = preferredlocationDistanceMap;
this.preferredlocation = preferredlocation;
}
// ************************************************************************
// Complex methods
// ************************************************************************
// ************************************************************************
// Complex methods
// ************************************************************************
/**
* @return route of the vehicle
*/
/**
* @return route of the vehicle
*/
@JsonIgnore
public List<Location> getRoute() {
if (customerList.isEmpty()) {
return Collections.emptyList();
}
List<Location> route = new ArrayList<Location>();
route.add(depot.getLocation());
for (Customer customer : customerList) {
route.add(customer.getLocation());
}
return route;
}
/**
* 总路线距离
*
* @return
*/
public long getTotalDistanceMeters() {
if (customerList.isEmpty()) {
return 0;
}
long totalDistance = 0;
Location previousLocation = depot.getLocation();
for (Customer customer : customerList) {
totalDistance += previousLocation.getDistanceTo(customer.getLocation());
previousLocation = customer.getLocation();
}
totalDistance += previousLocation.getDistanceTo(depot.getLocation());
return totalDistance;
}
public List<Location> getRoute() {
if (customerList.isEmpty()) {
return Collections.emptyList();
}
List<Location> route = new ArrayList<Location>();
route.add(depot.getLocation());
for (Customer customer : customerList) {
route.add(customer.getLocation());
}
return route;
}
/**
* 总路线距离
*
* @return
*/
public long getTotalDistanceMeters() {
if (customerList.isEmpty()) {
return 0;
}
long totalDistance = 0;
Location previousLocation = depot.getLocation();
for (Customer customer : customerList) {
totalDistance += previousLocation.getDistanceTo(customer.getLocation());
previousLocation = customer.getLocation();
}
totalDistance += previousLocation.getDistanceTo(depot.getLocation());
return totalDistance;
}
/**
* 获取偏好总距离 所有customer与该技术员的PreferredLocation距离之和
*
......@@ -117,11 +118,11 @@ public class Technician {
}
return totalDistance;
}
public int getCustomerSize() {
return customerList.size();
}
/**
* 获取总上班时间,第一个订单到最后一个订单时间跨度
*
......@@ -136,7 +137,7 @@ public class Technician {
- customerList.get(0).getArrivalTime();
}
}
/**
* 获取下班时间,最后一个订单完成时间
*
......@@ -151,8 +152,6 @@ public class Technician {
return lastCustomer.getDepartureTime();
}
}
@Override
public int hashCode() {
......@@ -169,7 +168,7 @@ public class Technician {
return true;
return this.id == ((Technician) obj).getId();
}
@Override
public String toString() {
return "Technician{" + "id=" + id + '}';
......
......@@ -16,45 +16,21 @@
package com.dituhui.pea.dispatch.service.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.UncheckedIOException;
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.score.ScoreExplanation;
import org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScore;
import org.optaplanner.core.api.solver.SolutionManager;
import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.api.solver.event.BestSolutionChangedEvent;
import org.optaplanner.core.api.solver.event.SolverEventListener;
import org.optaplanner.core.config.solver.SolverConfig;
import org.optaplanner.core.config.solver.termination.TerminationConfig;
import org.optaplanner.persistence.jackson.impl.domain.solution.JacksonSolutionFileIO;
import org.optaplanner.core.impl.solver.DefaultSolverFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.dituhui.pea.common.Result;
import com.dituhui.pea.dispatch.constraint.DispatchConstraintProvider;
import com.dituhui.pea.dispatch.pojo.Customer;
import com.dituhui.pea.dispatch.pojo.Depot;
import com.dituhui.pea.dispatch.eventListener.DispatchSolverEventListener;
import com.dituhui.pea.dispatch.pojo.DispatchSolution;
import com.dituhui.pea.dispatch.pojo.Location;
import com.dituhui.pea.dispatch.pojo.Technician;
import com.dituhui.pea.dispatch.service.DispatchService;
import com.dituhui.pea.dispatch.utils.DataUtils;
import com.dituhui.pea.dispatch.utils.DispatchSolutionUtils;
/**
* @author gpzhang
......@@ -65,365 +41,22 @@ public class DispatchServiceImpl implements DispatchService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public Result<?> manualDispatch(long unimprovedSecondsSpentLimit, long secondsSpentLimit) throws UncheckedIOException, FileNotFoundException {
logger.info("{}", "invoke manualDispatch");
public Result<?> manualDispatch(long unimprovedSecondsSpentLimit, long secondsSpentLimit)
throws UncheckedIOException, FileNotFoundException {
logger.info("{}", "manual dispatch begin");
// 创建解决方案对象
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();
DispatchSolution problem = createVehicleRoutingSolution(customerIndexMap, technicianIndexMap,
technicianCodeSkillsMap, customerCodeSkillMap, preferredlocationDistanceMap,
customerCodeServiceTimeMap);
// 创建求解器配置
// 创建 SolverConfig 对象,并设置求解器配置
SolverConfig solverConfig = new SolverConfig();
solverConfig.setSolutionClass(DispatchSolution.class);
solverConfig.withEntityClassList(Arrays.asList(Technician.class, Customer.class));// 这里不能漏掉,否则约束不生效
TerminationConfig terminationConfig = new TerminationConfig();
// 运行时长提升效果
// 5s/60s: 技能权重4-技能匹配问题0个,时间窗问题14个
// 10s/300s: 技能权重4-技能匹配问题0个,时间窗问题9个
terminationConfig
.setUnimprovedSecondsSpentLimit(unimprovedSecondsSpentLimit == 0 ? 5 : unimprovedSecondsSpentLimit);// XX秒没有找到更好方案
terminationConfig.setSecondsSpentLimit(secondsSpentLimit == 0 ? 60 : secondsSpentLimit);// 总时间不能超过XXs
solverConfig.withTerminationConfig(terminationConfig);
// 约束条件
solverConfig.withConstraintProviderClass(DispatchConstraintProvider.class);
DispatchSolution problem = DataUtils.getInitialProblem();
// 创建求解器
SolverFactory<DispatchSolution> solverFactory = SolverFactory.create(solverConfig);
DefaultSolverFactory<DispatchSolution> solverFactory = DispatchSolutionUtils
.getSolverFactory(unimprovedSecondsSpentLimit, secondsSpentLimit);
Solver<DispatchSolution> solver = solverFactory.buildSolver();
SolutionManager<DispatchSolution, HardSoftLongScore> scoreManager = SolutionManager.create(solverFactory);
solver.addEventListener(new SolverEventListener<DispatchSolution>() {
public void bestSolutionChanged(BestSolutionChangedEvent<DispatchSolution> event) {
// System.out.printf("found better score:%s at time:%ss %n", event.getNewBestScore().toShortString(),
// event.getTimeMillisSpent() / 1000);
System.out.printf("%s,%s%n", event.getNewBestScore().toLevelDoubles()[0],
event.getTimeMillisSpent() / 1000F);
}
});
// 得分监听器
solver.addEventListener(new DispatchSolverEventListener());
// 求解
DispatchSolution solution = solver.solve(problem);
printSolution(solution, customerIndexMap, technicianIndexMap);
System.out.println("final Score: " + solution.getScore().toShortString());
// Create a JacksonSolutionFileIO instance.
JacksonSolutionFileIO<DispatchSolution> exporter = new JacksonSolutionFileIO<DispatchSolution>(
DispatchSolution.class);
// Set the output file.
exporter.write(solution, new File("dispatchSolution.json"));
// Obtain a ScoreExplanation object for the best solution
// Using score calculation outside the Solver
// https://www.optaplanner.org/docs/optaplanner/latest/score-calculation/score-calculation.html
ScoreExplanation<DispatchSolution, HardSoftLongScore> scoreExplanation = scoreManager.explain(solution);
System.out.println(scoreExplanation.getSummary());
// Map<String, ConstraintMatchTotal<HardSoftLongScore>> constraintMatchTotalMap = scoreExplanation
// .getConstraintMatchTotalMap();
// constraintMatchTotalMap.forEach((key, value) -> {
// System.out.println(key + ":" + value.getConstraintName() + ":" + value.getScore().toShortString());
// });
return Result.success(solution.getTechnicianList());
}
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 DispatchSolution 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 {
DispatchSolution vehicleRoutingSolution = new DispatchSolution();
// 翻转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> pathTimeMatrixlines = IOUtils.readLines(new FileInputStream("data/pathTimeMatrix.csv"), "utf-8");
long[][] pathTimeMatrix = new long[customerIndexMap.keySet().size() + 1][customerIndexMap.keySet().size() + 1];
for (int i = 0; i < pathTimeMatrixlines.size(); i++) {
String line = pathTimeMatrixlines.get(i);
String[] temps = line.split(",");
for (int j = 0; j < temps.length; j++) {
// 秒转分钟
pathTimeMatrix[i][j] = (long) (Math.round(Float.parseFloat(temps[j]) / 60));
}
}
for (int i = 0; i < pathTimeMatrix.length; i++) {
Location locationi = locationIndex.get(i + 1);
for (int j = 0; j < pathTimeMatrix[i].length; j++) {
Location locationj = locationIndex.get(j + 1);
locationi.getDistanceTimeMap().put(locationj, pathTimeMatrix[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(DispatchSolution 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');
}
}
package com.dituhui.pea.dispatch.utils;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RegExUtils;
import com.dituhui.pea.dispatch.pojo.Customer;
import com.dituhui.pea.dispatch.pojo.Depot;
import com.dituhui.pea.dispatch.pojo.DispatchSolution;
import com.dituhui.pea.dispatch.pojo.Location;
import com.dituhui.pea.dispatch.pojo.Technician;
public class DataUtils {
/**
* 获取初始化测试数据
*
* @return
* @throws UncheckedIOException
* @throws FileNotFoundException
*/
public static DispatchSolution getInitialProblem() throws UncheckedIOException, FileNotFoundException {
Map<Integer, String> customerIndexMap = loadCustomerIndex();
Map<Integer, String> customerIndexXyMap = loadCustomerIndexXY();
Map<Integer, String> technicianIndexMap = loadTechnicianIndex();
Map<String, Set<String>> technicianCodeSkillsMap = loadTechnicianCodeSkillsMap();
Map<String, String> customerCodeSkillMap = loadCustomerCodeSkillMap();
// 偏好中心点位置
Map<String, String> technicianCodePreferredLocationMap = loadPreferredlocationMap();
Map<String, Map<String, Long>> preferredlocationDistanceMap = loadPreferredlocationDistanceMap(
technicianCodePreferredLocationMap);
Map<String, Integer> customerCodeServiceTimeMap = loadCustomerCodeServiceTimeMap();
DispatchSolution problem = createVehicleRoutingSolution(customerIndexMap, customerIndexXyMap,
technicianIndexMap, technicianCodeSkillsMap, customerCodeSkillMap, technicianCodePreferredLocationMap,
preferredlocationDistanceMap, customerCodeServiceTimeMap);
return problem;
}
private static Map<String, String> loadPreferredlocationMap() 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]);
}
return technicianCodeLocationMap;
}
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(
Map<String, String> technicianCodeLocationMap) throws UncheckedIOException, FileNotFoundException {
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
*/
private 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 Map<Integer, String> loadCustomerIndexXY() throws UncheckedIOException, FileNotFoundException {
List<String> customerCodeXYlines = IOUtils.readLines(new FileInputStream("data/customerIndexXY.csv"), "utf-8");
Map<Integer, String> customerIndexXYMap = new HashMap<Integer, String>();// 序号-code
for (int i = 0; i < customerCodeXYlines.size(); i++) {
String line = customerCodeXYlines.get(i);
String[] temps = line.split(",");
customerIndexXYMap.put(i + 1,
RegExUtils.removeAll(temps[1], "\"") + "," + RegExUtils.removeAll(temps[2], "\""));
}
return customerIndexXYMap;
}
private static DispatchSolution createVehicleRoutingSolution(Map<Integer, String> customerIndexMap,
Map<Integer, String> customerIndexXyMap, Map<Integer, String> technicianIndexMap,
Map<String, Set<String>> technicianCodeSkillsMap, Map<String, String> customerCodeSkillMap,
Map<String, String> technicianCodePreferredLocationMap,
Map<String, Map<String, Long>> preferredlocationDistanceMap,
Map<String, Integer> customerCodeServiceTimeMap) throws UncheckedIOException, FileNotFoundException {
DispatchSolution vehicleRoutingSolution = new DispatchSolution();
// 翻转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 ~ N+1
String xyString = customerIndexXyMap.get(i + 1);
String[] temps = xyString.split(",");
locationIndex.put(i + 1, new Location(i + 1, Float.parseFloat(temps[0]), Float.parseFloat(temps[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> pathTimeMatrixlines = IOUtils.readLines(new FileInputStream("data/pathTimeMatrix.csv"), "utf-8");
long[][] pathTimeMatrix = new long[customerIndexMap.keySet().size() + 1][customerIndexMap.keySet().size() + 1];
for (int i = 0; i < pathTimeMatrixlines.size(); i++) {
String line = pathTimeMatrixlines.get(i);
String[] temps = line.split(",");
for (int j = 0; j < temps.length; j++) {
// 秒转分钟
pathTimeMatrix[i][j] = (long) (Math.round(Float.parseFloat(temps[j]) / 60));
}
}
for (int i = 0; i < pathTimeMatrix.length; i++) {
Location locationi = locationIndex.get(i + 1);
for (int j = 0; j < pathTimeMatrix[i].length; j++) {
Location locationj = locationIndex.get(j + 1);
locationi.getDistanceTimeMap().put(locationj, pathTimeMatrix[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);
}
String xyString = technicianCodePreferredLocationMap.get(technicianIndexMap.get(i + 1));
String[] temps = xyString.split(",");
Location preferredlocation = new Location(i + 1, Float.parseFloat(RegExUtils.removeAll(temps[0], "\"")),
Float.parseFloat(RegExUtils.removeAll(temps[1], "\"")));
technicianList.add(new Technician(i + 1, technicianIndexMap.get(i + 1), depot, 480, 1080, skills,
preferredlocationDistanceMap.get(technicianIndexMap.get(i + 1)), preferredlocation));
}
vehicleRoutingSolution.setCustomerList(customerList);
vehicleRoutingSolution.setDepot(depot);
vehicleRoutingSolution.setLocationList(new ArrayList<>(locationIndex.values()));
vehicleRoutingSolution.setTechnicianList(technicianList);
return vehicleRoutingSolution;
}
}
package com.dituhui.pea.dispatch.utils;
import static java.util.Comparator.comparing;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.optaplanner.constraint.streams.drools.DroolsConstraintStreamScoreDirector;
import org.optaplanner.core.api.score.ScoreExplanation;
import org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScore;
import org.optaplanner.core.api.score.constraint.ConstraintMatch;
import org.optaplanner.core.api.score.constraint.ConstraintMatchTotal;
import org.optaplanner.core.api.solver.SolutionManager;
import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.config.score.director.ScoreDirectorFactoryConfig;
import org.optaplanner.core.config.solver.SolverConfig;
import org.optaplanner.core.config.solver.termination.TerminationConfig;
import org.optaplanner.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import org.optaplanner.core.impl.heuristic.selector.move.generic.list.ListChangeMove;
import org.optaplanner.core.impl.score.director.ScoreDirectorFactory;
import org.optaplanner.core.impl.solver.DefaultSolverFactory;
import org.optaplanner.persistence.jackson.impl.domain.solution.JacksonSolutionFileIO;
import com.dituhui.pea.dispatch.constraint.ConstraintNameEnum;
import com.dituhui.pea.dispatch.constraint.DispatchConstraintProvider;
import com.dituhui.pea.dispatch.pojo.Customer;
import com.dituhui.pea.dispatch.pojo.DispatchSolution;
import com.dituhui.pea.dispatch.pojo.Technician;
public class DispatchSolutionUtils {
/**
* 打印方案
*
* @param solution
*/
public static void printSolution(DispatchSolution solution) {
System.out.println("Score: " + solution.getScore().toShortString());
System.out.println("技能约束:");
solution.getTechnicianList().forEach(technician -> {
System.out.printf("技术员%s(%s) %s%n", technician.getId(), technician.getCode(), technician.getSkills());
for (Customer customer : technician.getCustomerList()) {
if (!technician.getSkills().contains(customer.getRequiredSkill())) {
// no match
System.err.printf(" 预约单%s(%s) %s%n", customer.getId(), customer.getCode(),
customer.getRequiredSkill());
} else {
System.out.printf(" 预约单%s(%s) %s%n", customer.getId(), customer.getCode(),
customer.getRequiredSkill());
}
}
});
AtomicInteger totalNum = new AtomicInteger(0);
solution.getTechnicianList().forEach(technician -> {
System.out.printf("技术员%s(%s) [%s,%s]%n", technician.getId(), technician.getCode(),
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(), customer.getCode(),
// 预约时间窗
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');
}
/**
* 生成方案地图页
*
* @param solution
* @param filename
*/
public static void exportMapHtml(DispatchSolution solution, String filename) {
try {
// 仓库起点
String depot = "\"" + solution.getDepot().getLocation().getX() + ","
+ solution.getDepot().getLocation().getY() + "\"";
// 技术员路线
String lines_ = "[";
for (Technician technician : solution.getTechnicianList()) {
lines_ += "\"" + technician.getCustomerList().stream()
.map(c -> c.getLocation().getX() + "," + c.getLocation().getY()).reduce((a, b) -> a + ";" + b)
.get() + "\",";
}
lines_ += "]";
final String lines = lines_;
// 技术员偏好中心点
String preferredlocation = "\"" + solution.getTechnicianList().stream()
.map(c -> c.getPreferredlocation().getX() + "," + c.getPreferredlocation().getY())
.reduce((a, b) -> a + ";" + b).get() + "\"";
List<String> dispatchMapLines = IOUtils.readLines(new FileInputStream("data/dispatchMap.html"), "GBK");
dispatchMapLines = dispatchMapLines.stream().map(line -> {
if (StringUtils.startsWith(line, " var depot = ")) {
return " var depot = " + depot;
} else if (StringUtils.startsWith(line, " var preferredlocation = ")) {
return " var preferredlocation = " + preferredlocation;
} else if (StringUtils.startsWith(line, " var lines = ")) {
return " var lines = " + lines;
} else {
return line;
}
}).collect(Collectors.toList());
IOUtils.writeLines(dispatchMapLines, "\r\n", new FileOutputStream(filename + ".html"), "GBK");
System.out.println("output map : " + filename);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 计算方案分数
*
* @param solution
* @return
*/
public static HardSoftLongScore getScore(DispatchSolution solution) {
DefaultSolverFactory<DispatchSolution> solverFactory = getSolverFactory();
ScoreDirectorFactory<DispatchSolution> scoreDirectorFactory = solverFactory.getScoreDirectorFactory();
DroolsConstraintStreamScoreDirector<DispatchSolution, HardSoftLongScore> scoreDirector = (DroolsConstraintStreamScoreDirector<DispatchSolution, HardSoftLongScore>) scoreDirectorFactory
.buildScoreDirector();
// Set the working solution of the ScoreDirector object.
scoreDirector.setWorkingSolution(solution);
// Iterate over the constraints in your problem and call the
// `addConstraintMatch()` method
// for each constraint that is violated by the solution.
// for (Constraint constraint : myConstraints) {
// if (constraint.isViolated(mySolution)) {
// scoreDirector.addConstraintMatch(constraint, constraint.getScore());
// }
// }
// Calculate the score of the solution.
HardSoftLongScore score = (HardSoftLongScore) scoreDirector.calculateScore();
return score;
}
/**
* 移动某个技术员的订单,返回新方案
*
* @param solution
* @param source 技术员
* @param sourceIndex 0开始
* @param destinationIndex 0开始
* @return
*/
public static DispatchSolution moveCustomer(DefaultSolverFactory<DispatchSolution> solverFactory,
DispatchSolution solution, Technician source, int sourceIndex, int destinationIndex) {
if (null == solverFactory) {
solverFactory = getSolverFactory();
}
ScoreDirectorFactory<DispatchSolution> scoreDirectorFactory = solverFactory.getScoreDirectorFactory();
DroolsConstraintStreamScoreDirector<DispatchSolution, HardSoftLongScore> scoreDirector = (DroolsConstraintStreamScoreDirector<DispatchSolution, HardSoftLongScore>) scoreDirectorFactory
.buildScoreDirector();
// Make a change to the solution.
// doMove(), doSwap(), or doChangeVariable()
// Create a variable descriptor for the "customerList" list property
ListVariableDescriptor<DispatchSolution> customerDescriptor = (ListVariableDescriptor<DispatchSolution>) scoreDirector
.getSolutionDescriptor().getEntityDescriptorStrict(Technician.class)
.getVariableDescriptor("customerList");
// Create the ListChangeMove
ListChangeMove<DispatchSolution> move = new ListChangeMove<DispatchSolution>(customerDescriptor, source,
sourceIndex, source, destinationIndex);
// apply the move and re-evaluate the score
scoreDirector.doAndProcessMove(move, true);
return scoreDirector.getWorkingSolution();
}
public static DefaultSolverFactory<DispatchSolution> getSolverFactory() {
return getSolverFactory(0, 0);
}
public static DefaultSolverFactory<DispatchSolution> getSolverFactory(long unimprovedSecondsSpentLimit,
long secondsSpentLimit) {
// 创建求解器配置
// 创建 SolverConfig 对象,并设置求解器配置
SolverConfig solverConfig = new SolverConfig();
solverConfig.setSolutionClass(DispatchSolution.class);
solverConfig.withEntityClassList(Arrays.asList(Technician.class, Customer.class));// 这里不能漏掉,否则约束不生效
TerminationConfig terminationConfig = new TerminationConfig();
terminationConfig
.setUnimprovedSecondsSpentLimit(unimprovedSecondsSpentLimit <= 0 ? 5 : unimprovedSecondsSpentLimit);// XX秒没有找到更好方案
terminationConfig.setSecondsSpentLimit(secondsSpentLimit <= 0 ? 60 : secondsSpentLimit);// 总时间不能超过XXs
solverConfig.withTerminationConfig(terminationConfig);
// 约束条件
solverConfig.withConstraintProviderClass(DispatchConstraintProvider.class);
solverConfig.setScoreDirectorFactoryConfig(
new ScoreDirectorFactoryConfig().withConstraintProviderClass(DispatchConstraintProvider.class));
DefaultSolverFactory<DispatchSolution> solverFactory = (DefaultSolverFactory<DispatchSolution>) SolverFactory
.<DispatchSolution>create(solverConfig);
return solverFactory;
}
/**
* 打印详细约束得分
*
* @param solution
*/
public static void explainSolutionConstraintDetail(DispatchSolution solution) {
DefaultSolverFactory<DispatchSolution> solverFactory = getSolverFactory();
// Obtain a ScoreExplanation object for the best solution
// Using score calculation outside the Solver
// https://www.optaplanner.org/docs/optaplanner/latest/score-calculation/score-calculation.html
SolutionManager<DispatchSolution, HardSoftLongScore> scoreManager = SolutionManager.create(solverFactory);
ScoreExplanation<DispatchSolution, HardSoftLongScore> scoreExplanation = scoreManager.explain(solution);
// System.out.println(scoreExplanation.getSummary());
Map<String, ConstraintMatchTotal<HardSoftLongScore>> constraintMatchTotalMap = scoreExplanation
.getConstraintMatchTotalMap();
constraintMatchTotalMap.forEach((key, value) -> {
if (!value.getScore().isFeasible()) {
// 违反硬约束
System.out.printf("%s 匹配%s次 hard得分:%s%n", value.getConstraintName(), value.getConstraintMatchCount(),
value.getScore().hardScore());
} else {
// 软约束
System.out.printf("%s 匹配%s次 soft得分:%s%n", value.getConstraintName(), value.getConstraintMatchCount(),
value.getScore().softScore());
}
value.getConstraintMatchSet().stream().sorted(comparing(ConstraintMatch::getScore))
.forEach(constraintMatch -> {
String text = "";
switch (ConstraintNameEnum.valueOf(value.getConstraintName())) {
case skillMatch:
case customerTimeWindowsMatch:
for (Object indictedObject : constraintMatch.getIndictedObjectList()) {
// 违反硬约束对象,根据具体约束返回不同类型对象
if (indictedObject instanceof Customer) {
Customer customer = (Customer) indictedObject;
text += customer.getCode() + ",";
}
}
System.out.printf(" 预约单(%s)违反约束,扣分%s%n", text, constraintMatch.getScore().toShortString());
break;
case totalDistance:
case preferredTotalDistance:
for (Object indictedObject : constraintMatch.getIndictedObjectList()) {
// 违反软约束对象,根据具体约束返回不同类型对象
if (indictedObject instanceof Technician) {
Technician technician = (Technician) indictedObject;
text += technician.getCode() + ",";
}
}
System.out.printf(" 技术员(%s)总路程得分%s%n", text, constraintMatch.getScore().toShortString());
break;
case technicianBalanceSoft:
if (constraintMatch.getScore().softScore() < 0) {
for (Object indictedObject : constraintMatch.getIndictedObjectList()) {
// 违反软约束对象,根据具体约束返回不同类型对象
if (indictedObject instanceof Technician) {
Technician technician = (Technician) indictedObject;
text += technician.getCode() + ",";
}
}
System.out.printf(" 技术员(%s)订单量差距得分%s%n", text,
constraintMatch.getScore().toShortString());
}
break;
default:
break;
}
});
});
}
/**
* 导出json结构
*
* @param solution
* @param fileName
*/
public static void exportSolutionJson(DispatchSolution solution, String fileName) {
// Create a JacksonSolutionFileIO instance.
JacksonSolutionFileIO<DispatchSolution> exporter = new JacksonSolutionFileIO<DispatchSolution>(
DispatchSolution.class);
// Set the output file.
exporter.write(solution, new File(fileName));
}
}
depot,"120.614853,31.338807"
106330400,"120.982906,31.440902"
106632743,"120.726916,31.592422"
106632993,"120.868246,31.041383"
106704506,"120.625144,31.257837"
106729637,"120.959821,31.402083"
106732179,"121.077572,31.563578"
106737923,"120.622214,31.333417"
106758769,"120.731802,31.300809"
106765790,"120.847507,31.42479"
106790276,"120.606255,31.268432"
106798348,"120.959821,31.402083"
106845767,"120.687214,31.253326"
106848936,"120.679366,31.287809"
106854117,"120.664649,31.316367"
106855369,"120.616312,31.385716"
106865528,"120.623197,31.470323"
106867266,"120.597917,31.348755"
106879505,"120.765083,31.697774"
106882180,"120.664487,31.282887"
106893582,"121.107535,31.479541"
106228451,"120.721813,31.303003"
106660472,"120.611127,31.250817"
106684680,"120.626474,31.224566"
106703025,"120.530752,31.281024"
106762173,"120.606634,31.292016"
106802404,"120.654614,31.098804"
106820041,"120.719909,31.382979"
106820735,"120.58446,31.322863"
106824266,"120.697424,31.344814"
106828070,"120.596177,31.283586"
106833813,"120.523634,31.858481"
106835282,"121.039728,31.400563"
106853363,"121.104428,31.453363"
106856986,"120.745434,31.304664"
106861402,"120.785598,31.097726"
106863823,"120.966152,31.389332"
106870737,"120.612394,31.194301"
106870765,"120.451477,31.938889"
106871040,"120.686309,31.327041"
106872591,"120.591992,31.292224"
106872658,"120.768159,31.337805"
106875426,"120.64272,31.277918"
106890613,"120.526677,31.863775"
106893208,"120.748525,31.324368"
106279591,"120.982906,31.440902"
106704564,"120.625144,31.257837"
106729640,"120.959821,31.402083"
106732181,"121.077572,31.563578"
106738036,"120.622214,31.333417"
106758988,"120.731802,31.300809"
106765761,"120.847507,31.42479"
106818668,"120.493618,31.24607"
106823211,"120.651719,31.278747"
106824512,"120.754292,31.337413"
106836405,"120.665415,31.32013"
106841840,"120.615365,31.186788"
106851428,"120.98225,31.417492"
106852112,"120.654228,31.337012"
106853178,"120.632811,31.173964"
106854359,"120.84248,31.284855"
106855674,"120.743073,31.677162"
106856390,"120.655769,31.393115"
106859149,"120.724624,31.487646"
106859862,"120.549426,31.376686"
106860870,"120.748602,31.317585"
106862449,"120.777722,31.636159"
106865892,"120.664487,31.282887"
106866730,"120.606376,31.391923"
106868422,"120.702768,31.333509"
106870053,"120.732045,31.30597"
106873767,"120.964531,31.349257"
106873992,"120.574297,31.321478"
106876557,"120.721418,31.487407"
106876682,"121.088316,31.5656"
106881724,"120.601868,31.148427"
106883231,"121.120276,31.465135"
106883391,"120.861145,31.080919"
106888947,"120.628533,31.368943"
106889389,"120.680358,31.325299"
106893581,"121.107535,31.479541"
106776742,"120.70851,31.248646"
106820178,"120.678376,31.149701"
106826035,"120.49468,31.996066"
106835053,"120.600039,31.295101"
106839639,"120.883187,31.287842"
106840295,"120.526884,31.373801"
106848186,"121.135103,31.441649"
106869743,"120.938132,31.396139"
106871570,"120.634245,31.188141"
106871918,"120.64205,31.296388"
106872059,"120.589301,31.3104"
106874727,"120.748439,31.684466"
106879292,"120.715751,31.29945"
106881156,"120.566187,31.855965"
106884799,"120.605229,31.394392"
106886425,"120.548647,31.842596"
106890832,"120.604399,31.349966"
106576847,"121.106111,31.480717"
106704602,"120.625144,31.257837"
106729639,"120.959821,31.402083"
106732180,"121.077572,31.563578"
106738035,"120.622214,31.333417"
106853161,"120.632811,31.173964"
106855710,"120.743073,31.677162"
106856389,"120.655769,31.393115"
106859148,"120.724624,31.487646"
106860869,"120.748602,31.317585"
106862447,"120.777722,31.636159"
106866729,"120.606376,31.391923"
106868053,"120.606634,31.292016"
106888946,"120.628533,31.368943"
106854169,"120.634382,31.374593"
106870713,"120.633518,31.153463"
106891293,"120.636053,31.289328"
106549827,"121.144142,31.422735"
106765447,"120.616316,31.402105"
106781641,"120.606019,31.398212"
106793056,"120.747643,31.310646"
106803268,"120.607062,31.398126"
106818600,"121.144926,31.461553"
106822716,"120.705113,31.257879"
106829323,"120.673489,31.297058"
106840366,"120.649177,31.303238"
106847687,"120.530373,31.853617"
106866881,"120.629969,31.270552"
106870492,"120.650458,31.225728"
106871200,"120.59747,31.285217"
106872822,"120.616482,31.402948"
106878500,"120.606019,31.398212"
106880124,"120.615837,31.316617"
106880969,"120.677186,31.149534"
106892125,"120.587065,31.283529"
106763043,"120.687676,31.443469"
106801344,"120.69381,31.304455"
106806874,"120.650618,31.176803"
106824742,"120.77035,31.312694"
106858813,"120.769821,31.342065"
106870383,"120.624333,31.361689"
106632622,"120.726916,31.592422"
106744960,"121.077777,31.560303"
106809648,"120.956653,31.459789"
106862554,"120.685176,31.471252"
106649991,"120.632112,31.286827"
106753590,"120.597212,31.294007"
106845946,"120.747488,31.340815"
106859379,"120.756575,31.336275"
106632543,"120.726916,31.592422"
106632849,"120.726916,31.592422"
106665842,"120.64402,31.77468"
106730931,"120.639926,31.306425"
106744959,"121.077777,31.560303"
106809662,"120.956653,31.459789"
106813589,"120.844551,31.013903"
106862553,"120.685176,31.471252"
106871868,"120.686193,31.327165"
106879520,"120.614752,31.309742"
106694001,"120.632112,31.286827"
106828628,"120.77099,31.682885"
106834398,"120.761397,31.311754"
106886055,"120.598933,31.297181"
106665727,"120.64402,31.77468"
106665740,"120.64402,31.77468"
106704534,"120.625144,31.257837"
106809681,"120.956653,31.459789"
106862552,"120.685176,31.471252"
106884214,"120.620985,31.129614"
106884485,"120.485939,31.334295"
106885700,"120.727943,31.254621"
106887147,"120.790707,31.683097"
106754377,"120.639734,31.32923"
106775190,"120.612111,31.189908"
106849556,"120.605103,31.296943"
106867319,"120.696831,31.275636"
106888973,"120.898629,31.749332"
106889146,"120.60652,31.404533"
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gbk">
<title>算法测试</title>
<script type="text/javascript" src="http://webapi.amap.com/maps?v=1.4.15&key=b64407a9a60c945f80270b17f9c4eabd"></script>
<style>
#iMap {
height: 560px;
width: 100%;
float: left;
}
.info {
padding: .75rem 1.25rem;
margin-bottom: 1rem;
border-radius: .25rem;
position: fixed;
top: 1rem;
background-color: white;
width: auto;
min-width: 22rem;
border-width: 0;
right: 1rem;
box-shadow: 0 2px 6px 0 rgba(114, 124, 245, .5);
}
label {
width: 80px;
float: left;
}
.detail {
padding: 10px;
border: 1px solid #aaccaa;
}
textarea {
width: 650px;
height: 100px;
}
.input-card {
display: flex;
flex-direction: column;
min-width: 0;
word-wrap: break-word;
background-color: #fff;
background-clip: border-box;
border-radius: .25rem;
width: 22rem;
border-width: 0;
border-radius: 0.4rem;
box-shadow: 0 2px 6px 0 rgba(114, 124, 245, .5);
position: fixed;
bottom: 1rem;
right: 1rem;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
padding: 0.75rem 1.25rem;
}
</style>
</head>
<body onload="mapInit()">
<div id="iMap"></div>
<div class="input-card" style="width:19rem">
<h4>设置地图显示要素(Features)</h4>
<div id="map-features">
<div class="input-item">
<input type='checkbox' name='mapStyle' value='bg' checked>
<span class="input-text">区域面(bg)</span>
</div>
<div class="input-item">
<input type='checkbox' name='mapStyle' value='road' checked>
<span class="input-text">道路(road)</span>
</div>
<div class="input-item">
<input type='checkbox' name='mapStyle' value='building' checked>
<span class="input-text">建筑物(building)</span>
</div>
<div class="input-item">
<input type='checkbox' name='mapStyle' value='point' checked>
<span class="input-text">标注(point)</span>
</div>
</div>
</div>
</body>
<script language="javascript">
var mapObj;
var lnglatXY;
var polygon;
//初始化地图
function mapInit() {
var opt = {
center: [120.61485,31.338806],
zoom: 10
}
mapObj = new AMap.Map("iMap", opt);
// 技术员路线
var colors = ["6EA7C1","6B55AE","E67FA2","FFEFA1","000000","4E4FEB","068FFF","EEEEEE","F2D8D8","5C8984","545B77","374259","525FE1","F86F03","FFA41B","FFF6F4","E90064","B3005E","FF5F9E","F29727"];
var lines = ["120.76816,31.337805;120.697426,31.344814;120.68631,31.327042;120.678375,31.1497;120.64272,31.277918;120.606636,31.292015;120.59893,31.29718;120.59721,31.294006;120.64205,31.296389;120.63973,31.32923;120.62434,31.361689;120.636055,31.289328","120.6044,31.349966;120.70851,31.248646;120.65462,31.098804;120.7856,31.097727;120.93813,31.39614;120.96615,31.389332;121.1351,31.441648;121.10443,31.453363;121.03973,31.400562;120.89863,31.749332","120.731804,31.30081;120.61632,31.402105;120.622215,31.333418;121.10754,31.479542;120.68517,31.471252;120.60602,31.398212;120.61584,31.316616","120.95982,31.402082;120.95665,31.45979;120.61631,31.385715;120.66465,31.316366;120.95982,31.402082;120.54942,31.376686","121.077576,31.563578;121.077774,31.560303;120.777725,31.63616;120.574295,31.321478;120.48594,31.334295;120.66449,31.282887;120.606636,31.292015;120.63281,31.173964;120.59747,31.285217","120.687675,31.443468;120.748436,31.684465;120.566185,31.855965;120.49468,31.996065;120.45148,31.938889;120.52668,31.863775;120.523636,31.85848;120.548645,31.842596;120.77099,31.682884;120.60523,31.394392","120.615364,31.186789;120.606255,31.268433;120.95982,31.402082;121.14493,31.461554;120.70512,31.25788;120.74764,31.310646;120.84248,31.284855","120.58707,31.28353;120.53037,31.853617;120.68722,31.253326;120.62997,31.270552","120.5893,31.3104;120.6051,31.296944;120.591995,31.292225;120.596176,31.283587;120.63211,31.286827;120.60004,31.295101;120.58446,31.322863;120.69683,31.275637;120.71991,31.382978;120.76982,31.342066;120.60652,31.404533","121.14414,31.422735;121.10754,31.479542;121.12028,31.465136;120.861145,31.08092;120.724625,31.487646;120.60187,31.148426","120.65577,31.393114;120.79071,31.683098;120.74307,31.677162;120.76508,31.697775;120.74307,31.677162;120.95982,31.402082;120.8475,31.42479;120.622215,31.333418;120.66449,31.282887","120.65423,31.337011;120.67349,31.297058;120.64918,31.303238;120.748604,31.317585;120.65577,31.393114;120.72142,31.487408;120.622215,31.333418;120.62099,31.129614;120.65046,31.225727","120.754295,31.337414;120.625145,31.257837;120.625145,31.257837;120.65172,31.278748;120.625145,31.257837;120.63281,31.173964;120.86825,31.041384;120.9829,31.440903;120.96453,31.349257","120.625145,31.257837;120.493614,31.24607;120.60706,31.398127;120.616486,31.402948;120.62853,31.368942;120.66541,31.32013;120.677185,31.149534;120.731804,31.30081","120.530754,31.281025;120.61113,31.250816;120.62647,31.224566;120.6124,31.194302;120.63425,31.18814;120.612114,31.189907;120.633514,31.153463;120.526886,31.3738;120.634384,31.374594;120.75658,31.336275;120.65062,31.176804","120.63211,31.286827;120.69381,31.304455;120.74544,31.304665;120.74749,31.340815;120.74853,31.324368;120.77035,31.312695;120.7614,31.311754;120.71575,31.29945;120.72181,31.303003;120.88319,31.287842","120.60638,31.391924;120.748604,31.317585;120.67937,31.28781;120.8475,31.42479;120.68036,31.325298;121.077576,31.563578;121.08832,31.5656;121.077576,31.563578;120.62853,31.368942","120.64402,31.77468;120.64402,31.77468;120.597916,31.348755;120.60638,31.391924;120.6232,31.470324;120.72691,31.592422;120.72691,31.592422;120.724625,31.487646;120.72794,31.254622","120.777725,31.63616;120.60602,31.398212;120.70277,31.33351;120.73205,31.30597;120.95665,31.45979;121.10611,31.480717;120.9829,31.440903;120.98225,31.417492;120.68517,31.471252","120.72691,31.592422;120.84455,31.013903;120.95665,31.45979;121.077774,31.560303;120.72691,31.592422;120.68517,31.471252;120.63992,31.306425;120.686195,31.327166;120.61475,31.309742;120.64402,31.77468"];
lines.forEach(function(line,index){
showLine(line,colors[index]);
});
// 仓库起点
var depot = "120.61485,31.338806";
addMarker2(depot);
// 技术员偏好中心点
var preferredlocation = "120.753876,31.312792;120.60937,31.37701;120.59335,31.314192;120.6781,31.312456;120.694275,31.230944;120.68786,31.631952;120.62794,31.25647;120.630035,31.153814;120.53737,31.36919;120.597626,31.18977;120.62713,31.29919;120.69743,31.234127;120.634636,31.298191;120.72335,31.296595;120.39913,31.233416;120.72677,31.3044;120.66996,31.272655;120.6085,31.126905;120.385025,31.351074;120.736176,31.337831";
preferredlocation.split(';').forEach(function(xy,index){
addCircle(xy,colors[index]);
});
}
// 画线
// 多边形轮廓线的节点坐标数组
function showLine(bounds,color) {
if ('' == bounds) {
alert("请输入坐标串");
return;
}
var path = bounds.split(';').map(xy => {
var segs = xy.split(',');
return new AMap.LngLat(segs[0], segs[1]);
});
// alert(path.length + "个坐标");
var polygon = new AMap.Polyline({
path: path,
strokeColor: '#'+color, // 颜色
strokeWeight: 5, // 线条宽度,默认为 1
strokeOpacity: 0.8, //线透明度
strokeStyle: 'solid', // 线条样式
showDir: true, //白色方向箭头
});
mapObj.add(polygon);
}
// 实例化点标记
function addMarker(bounds,image) {
//var bounds = document.getElementById("markers").value;
if ('' == bounds) {
alert("请输入坐标串");
return;
}
bounds.split(';').map(xy => {
var segs = xy.split(',');
var marker = new AMap.Marker({
icon: image,
position: [segs[0], segs[1]],
//anchor: 'bottom-center', //设置锚点
//offset: new AMap.Pixel(-13, -30)
});
marker.setMap(mapObj);
return new AMap.LngLat(segs[0], segs[1]);
});
}
// 实例化点标记
function addMarker2(bounds) {
if ('' == bounds) {
alert("请输入坐标串");
return;
}
// 创建一个 Icon
var startIcon = new AMap.Icon({
// 图标尺寸
size: new AMap.Size(25, 34),
// 图标的取图地址
image: 'http://a.amap.com/jsapi_demos/static/demo-center/icons/dir-marker.png',
// 图标所用图片大小
imageSize: new AMap.Size(135, 40),
// 图标取图偏移量
imageOffset: new AMap.Pixel(-9, -3)
});
bounds.split(';').map(xy => {
var segs = xy.split(',');
var marker = new AMap.Marker({
icon: startIcon,
position: [segs[0], segs[1]],
//anchor: 'bottom-center', //设置锚点
offset: new AMap.Pixel(-13, -30)
});
marker.setMap(mapObj);
return new AMap.LngLat(segs[0], segs[1]);
});
}
// 实例化圆形标记
function addCircle(bounds,color) {
if ('' == bounds) {
alert("请输入坐标串");
return;
}
bounds.split(';').map(xy => {
var segs = xy.split(',');
// 构造矢量圆形
var circle = new AMap.Circle({
center: new AMap.LngLat(segs[0], segs[1]), // 圆心位置
radius: 2000, //半径
strokeColor: "#"+color, //线颜色
strokeOpacity: 1, //线透明度
strokeWeight: 3, //线粗细度
fillColor: "#"+color, //填充颜色
fillOpacity: 0.8 //填充透明度
});
mapObj.add(circle);
return new AMap.LngLat(segs[0], segs[1]);
});
}
//设置地图显示要素
function setMapFeatures() {
var features = [];
var inputs = document.querySelectorAll("#map-features input");
inputs.forEach(function(input) {
if (input.checked) {
features.push(input.value);
}
});
mapObj.setFeatures(features);
}
//绑定checkbox点击事件
var inputs = document.querySelectorAll("#map-features input");
inputs.forEach(function(checkbox) {
checkbox.onclick = setMapFeatures;
});
</script>
</html>
\ No newline at end of file
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!