Skip to content
Toggle navigation
Projects
Groups
Snippets
Help
yangxiujun
/
paidan_demo
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit 1a09ae86
authored
Jul 10, 2023
by
张晓
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'develop' of
https://gitlab.dituhui.com/bsh/project/project
into develop
2 parents
5f0bc92b
9037490e
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
1397 additions
and
627 deletions
project-dispatch/src/main/java/com/dituhui/pea/dispatch/constraint/ConstraintNameEnum.java
project-dispatch/src/main/java/com/dituhui/pea/dispatch/constraint/DispatchConstraintProvider.java
project-dispatch/src/main/java/com/dituhui/pea/dispatch/eventListener/DispatchSolverEventListener.java
project-dispatch/src/main/java/com/dituhui/pea/dispatch/pojo/Location.java
project-dispatch/src/main/java/com/dituhui/pea/dispatch/pojo/Technician.java
project-dispatch/src/main/java/com/dituhui/pea/dispatch/service/impl/DispatchServiceImpl.java
project-dispatch/src/main/java/com/dituhui/pea/dispatch/utils/DataUtils.java
project-dispatch/src/main/java/com/dituhui/pea/dispatch/utils/DispatchSolutionUtils.java
project-dispatch/src/main/resources/application.yaml
project-dispatch/src/main/resources/data/customerIndexXY.csv
project-dispatch/src/main/resources/data/dispatchMap.html
project-dispatch/src/test/java/com/dituhui/pea/dispatch/test/Test.java
project-order/src/main/resources/application.yaml
project-dispatch/src/main/java/com/dituhui/pea/dispatch/constraint/ConstraintNameEnum.java
0 → 100644
View file @
1a09ae8
package
com
.
dituhui
.
pea
.
dispatch
.
constraint
;
/**
* 约束枚举
*
* @author zhangguoping
*
*/
public
enum
ConstraintNameEnum
{
// 硬约束
/**
* 技术员技能跟订单相匹配
*/
skillMatch
,
/**
* 技术员到达时间跟时间窗吻合
*/
customerTimeWindowsMatch
,
// 软约束
/**
* 订单数量均衡
*/
technicianBalanceSoft
,
/**
* 总路程最小
*/
totalDistance
,
/**
* 技术员中心点偏好
*/
preferredTotalDistance
}
project-dispatch/src/main/java/com/dituhui/pea/dispatch/constraint/DispatchConstraintProvider.java
View file @
1a09ae8
...
...
@@ -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
customerTimeWindowsMatch
1
(
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
())
/
120
F
))
.
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
()
/
1440
f
))
.
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
());
}
}
project-dispatch/src/main/java/com/dituhui/pea/dispatch/eventListener/DispatchSolverEventListener.java
0 → 100644
View file @
1a09ae8
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
}
}
project-dispatch/src/main/java/com/dituhui/pea/dispatch/pojo/Location.java
View file @
1a09ae8
...
...
@@ -13,6 +13,11 @@ import lombok.Setter;
public
class
Location
{
private
final
long
id
;
// 坐标点
private
float
x
;
private
float
y
;
private
String
code
;
// 类型 engineer order
private
String
type
;
...
...
@@ -27,6 +32,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
;
}
public
Location
(
long
id
,
String
code
,
String
type
,
double
longitude
,
double
latitude
)
{
this
.
id
=
id
;
...
...
project-dispatch/src/main/java/com/dituhui/pea/dispatch/pojo/Technician.java
View file @
1a09ae8
...
...
@@ -19,194 +19,180 @@ import lombok.Data;
@PlanningEntity
public
class
Technician
{
@PlanningId
private
long
id
;
private
String
code
;
@JsonIgnore
private
Depot
depot
;
// 上班时间窗 分钟480-1080 8-18点
private
int
startTime
;
private
int
endTime
;
// 技能
private
Set
<
String
>
skills
;
// 每日最大单量
private
int
maxCount
;
// 每日最大工作时长
private
int
maxMinute
;
// 单位是米,这里要注意
private
int
maxDistanceMeter
;
// 偏好坐标
// private Location preferredlocation;
// teck code : customer code , distance
@JsonIgnore
private
Map
<
String
,
Long
>
preferredlocationDistanceMap
=
new
HashMap
<
String
,
Long
>();
@PlanningListVariable
private
List
<
Customer
>
customerList
=
new
ArrayList
<>();
public
Technician
()
{
}
public
Technician
(
long
id
,
String
code
,
Depot
depot
,
int
startTime
,
int
endTime
,
Set
<
String
>
skills
,
Map
<
String
,
Long
>
preferredlocationDistanceMap
)
{
this
.
id
=
id
;
this
.
code
=
code
;
this
.
depot
=
depot
;
this
.
startTime
=
startTime
;
this
.
endTime
=
endTime
;
this
.
skills
=
skills
;
this
.
preferredlocationDistanceMap
=
preferredlocationDistanceMap
;
}
public
Technician
(
long
id
,
String
code
,
int
maxCount
,
int
maxMinute
,
int
maxDistanceMeter
,
Depot
depot
,
int
startTime
,
int
endTime
,
Set
<
String
>
skills
,
Map
<
String
,
Long
>
preferredlocationDistanceMap
)
{
this
.
id
=
id
;
this
.
code
=
code
;
this
.
depot
=
depot
;
this
.
startTime
=
startTime
;
this
.
endTime
=
endTime
;
this
.
skills
=
skills
;
this
.
maxCount
=
maxCount
;
this
.
maxMinute
=
maxMinute
;
this
.
maxDistanceMeter
=
maxDistanceMeter
;
this
.
preferredlocationDistanceMap
=
preferredlocationDistanceMap
;
}
// ************************************************************************
// Complex methods
// ************************************************************************
/**
* @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
;
}
/**
* 获取偏好总距离 所有customer与该技术员的PreferredLocation距离之和
*
* @return
*/
public
long
getPreferredTotalDistanceMeters
()
{
if
(
customerList
.
isEmpty
())
{
return
0
;
}
long
totalDistance
=
0
;
for
(
Customer
customer
:
customerList
)
{
totalDistance
+=
preferredlocationDistanceMap
.
get
(
customer
.
getCode
());
}
return
totalDistance
;
}
public
int
getCustomerSize
()
{
return
customerList
.
size
();
}
/**
* 获取总上班时间,第一个订单到最后一个订单时间跨度
*
* @return
*/
public
int
getWorkTime
()
{
int
size
=
customerList
.
size
();
if
(
0
==
size
)
{
return
0
;
}
else
{
return
customerList
.
get
(
size
-
1
).
getArrivalTime
()
+
customerList
.
get
(
size
-
1
).
getServiceDuration
()
-
customerList
.
get
(
0
).
getArrivalTime
();
}
}
/**
* 获取下班时间,最后一个订单完成时间
*
* @return
*/
public
int
getOffWorkTime
()
{
int
size
=
customerList
.
size
();
if
(
0
==
size
)
{
return
0
;
}
else
{
Customer
lastCustomer
=
customerList
.
get
(
size
-
1
);
return
lastCustomer
.
getDepartureTime
();
}
}
@Override
public
int
hashCode
()
{
return
Long
.
valueOf
(
this
.
id
).
hashCode
();
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
obj
==
null
)
return
false
;
if
(!(
obj
instanceof
Technician
))
return
false
;
if
(
obj
==
this
)
return
true
;
return
this
.
id
==
((
Technician
)
obj
).
getId
();
}
@Override
public
String
toString
()
{
return
"Technician{"
+
"id="
+
id
+
", code='"
+
code
+
'\''
+
", depot="
+
depot
+
", startTime="
+
startTime
+
", endTime="
+
endTime
+
", skills="
+
skills
+
", maxCount="
+
maxCount
+
", maxMinute="
+
maxMinute
+
", maxDistanceMeter="
+
maxDistanceMeter
+
'}'
;
}
@PlanningId
private
long
id
;
private
String
code
;
@JsonIgnore
private
Depot
depot
;
// 上班时间窗 分钟480-1080 8-18点
private
int
startTime
;
private
int
endTime
;
// 技能
private
Set
<
String
>
skills
;
// 偏好坐标
private
Location
preferredlocation
;
// technician code : customer code , distance
@JsonIgnore
private
Map
<
String
,
Long
>
preferredlocationDistanceMap
=
new
HashMap
<
String
,
Long
>();
// 每日最大单量
private
int
maxCount
;
// 每日最大工作时长
private
int
maxMinute
;
// 单位是米,这里要注意
private
int
maxDistanceMeter
;
@PlanningListVariable
private
List
<
Customer
>
customerList
=
new
ArrayList
<>();
public
Technician
()
{
}
public
Technician
(
long
id
,
String
code
,
Depot
depot
,
int
startTime
,
int
endTime
,
Set
<
String
>
skills
,
Map
<
String
,
Long
>
preferredlocationDistanceMap
,
Location
preferredlocation
)
{
this
.
id
=
id
;
this
.
code
=
code
;
this
.
depot
=
depot
;
this
.
startTime
=
startTime
;
this
.
endTime
=
endTime
;
this
.
skills
=
skills
;
this
.
preferredlocationDistanceMap
=
preferredlocationDistanceMap
;
this
.
preferredlocation
=
preferredlocation
;
}
public
Technician
(
long
id
,
String
code
,
int
maxCount
,
int
maxMinute
,
int
maxDistanceMeter
,
Depot
depot
,
int
startTime
,
int
endTime
,
Set
<
String
>
skills
,
Map
<
String
,
Long
>
preferredlocationDistanceMap
)
{
this
.
id
=
id
;
this
.
code
=
code
;
this
.
depot
=
depot
;
this
.
startTime
=
startTime
;
this
.
endTime
=
endTime
;
this
.
skills
=
skills
;
this
.
maxCount
=
maxCount
;
this
.
maxMinute
=
maxMinute
;
this
.
maxDistanceMeter
=
maxDistanceMeter
;
this
.
preferredlocationDistanceMap
=
preferredlocationDistanceMap
;
}
// ************************************************************************
// Complex methods
// ************************************************************************
/**
* @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
;
}
/**
* 获取偏好总距离 所有customer与该技术员的PreferredLocation距离之和
*
* @return
*/
public
long
getPreferredTotalDistanceMeters
()
{
if
(
customerList
.
isEmpty
())
{
return
0
;
}
long
totalDistance
=
0
;
for
(
Customer
customer
:
customerList
)
{
totalDistance
+=
preferredlocationDistanceMap
.
get
(
customer
.
getCode
());
}
return
totalDistance
;
}
public
int
getCustomerSize
()
{
return
customerList
.
size
();
}
/**
* 获取总上班时间,第一个订单到最后一个订单时间跨度
*
* @return
*/
public
int
getWorkTime
()
{
int
size
=
customerList
.
size
();
if
(
0
==
size
)
{
return
0
;
}
else
{
return
customerList
.
get
(
size
-
1
).
getArrivalTime
()
+
customerList
.
get
(
size
-
1
).
getServiceDuration
()
-
customerList
.
get
(
0
).
getArrivalTime
();
}
}
/**
* 获取下班时间,最后一个订单完成时间
*
* @return
*/
public
int
getOffWorkTime
()
{
int
size
=
customerList
.
size
();
if
(
0
==
size
)
{
return
0
;
}
else
{
Customer
lastCustomer
=
customerList
.
get
(
size
-
1
);
return
lastCustomer
.
getDepartureTime
();
}
}
@Override
public
int
hashCode
()
{
return
Long
.
valueOf
(
this
.
id
).
hashCode
();
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
obj
==
null
)
return
false
;
if
(!(
obj
instanceof
Technician
))
return
false
;
if
(
obj
==
this
)
return
true
;
return
this
.
id
==
((
Technician
)
obj
).
getId
();
}
@Override
public
String
toString
()
{
return
"Technician{"
+
"id="
+
id
+
", code='"
+
code
+
'\''
+
", depot="
+
depot
+
", startTime="
+
startTime
+
", endTime="
+
endTime
+
", skills="
+
skills
+
", maxCount="
+
maxCount
+
", maxMinute="
+
maxMinute
+
", maxDistanceMeter="
+
maxDistanceMeter
+
'}'
;
}
}
project-dispatch/src/main/java/com/dituhui/pea/dispatch/service/impl/DispatchServiceImpl.java
View file @
1a09ae8
...
...
@@ -16,44 +16,21 @@
package
com
.
dituhui
.
pea
.
dispatch
.
service
.
impl
;
import
java.io.*
;
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
java.io.FileNotFoundException
;
import
java.io.UncheckedIOException
;
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.core.io.ClassPathResource
;
import
org.springframework.stereotype.Service
;
import
org.springframework.util.ResourceUtils
;
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
...
...
@@ -63,366 +40,23 @@ public class DispatchServiceImpl implements DispatchService {
private
Logger
logger
=
LoggerFactory
.
getLogger
(
getClass
());
@Override
public
Result
<?>
manualDispatch
(
long
unimprovedSecondsSpentLimit
,
long
secondsSpentLimit
)
throws
UncheckedIOException
,
IOException
{
logger
.
info
(
"{}"
,
"invoke manualDispatch"
);
// 创建解决方案对象
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
);
// 创建求解器
SolverFactory
<
DispatchSolution
>
solverFactory
=
SolverFactory
.
create
(
solverConfig
);
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
()
/
1000
F
);
}
});
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());
// });
@Override
public
Result
<?>
manualDispatch
(
long
unimprovedSecondsSpentLimit
,
long
secondsSpentLimit
)
throws
UncheckedIOException
,
FileNotFoundException
{
logger
.
info
(
"{}"
,
"manual dispatch begin"
);
// 创建解决方案对象
DispatchSolution
problem
=
DataUtils
.
getInitialProblem
();
// 创建求解器
DefaultSolverFactory
<
DispatchSolution
>
solverFactory
=
DispatchSolutionUtils
.
getSolverFactory
(
unimprovedSecondsSpentLimit
,
secondsSpentLimit
);
Solver
<
DispatchSolution
>
solver
=
solverFactory
.
buildSolver
();
// 得分监听器
solver
.
addEventListener
(
new
DispatchSolverEventListener
());
// 求解
DispatchSolution
solution
=
solver
.
solve
(
problem
);
return
Result
.
success
(
solution
.
getTechnicianList
());
}
private
static
Map
<
String
,
Integer
>
loadCustomerCodeServiceTimeMap
()
throws
UncheckedIOException
,
IOException
{
List
<
String
>
customerServiceTime
=
IOUtils
.
readLines
(
new
ClassPathResource
(
"data/customerServiceTime.csv"
).
getInputStream
(),
"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
,
IOException
{
List
<
String
>
technicianCodeLocation
=
IOUtils
.
readLines
(
new
ClassPathResource
(
"data/technicianLocation.csv"
).
getInputStream
(),
"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
ClassPathResource
(
"data/customerLocation.csv"
).
getInputStream
(),
"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
>>
tecnicianCustomerDistanceMap
=
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
=
tecnicianCustomerDistanceMap
.
get
(
technicianCode
);
if
(
null
==
customerMaps
)
{
customerMaps
=
new
HashMap
<
String
,
Long
>();
tecnicianCustomerDistanceMap
.
put
(
technicianCode
,
customerMaps
);
}
customerMaps
.
put
(
customerCode
,
distance
);
});
});
return
tecnicianCustomerDistanceMap
;
}
/**
* 获取经纬度距离
*
* @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
,
IOException
{
List
<
String
>
customerSkill
=
IOUtils
.
readLines
(
new
ClassPathResource
(
"data/customerSkill.csv"
).
getInputStream
(),
"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
,
IOException
{
List
<
String
>
technicianSkills
=
IOUtils
.
readLines
(
new
ClassPathResource
(
"data/technicianSkills.csv"
).
getInputStream
(),
"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
,
IOException
{
List
<
String
>
technicianIndexlines
=
IOUtils
.
readLines
(
new
ClassPathResource
(
"data/technicianIndex.csv"
).
getInputStream
(),
"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
,
IOException
{
List
<
String
>
customerIndexlines
=
IOUtils
.
readLines
(
new
ClassPathResource
(
"data/customerIndex.csv"
).
getInputStream
(),
"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
,
IOException
{
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
ClassPathResource
(
"data/pathMatrix.csv"
).
getInputStream
(),
"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
ClassPathResource
(
"data/pathTimeMatrix.csv"
).
getInputStream
(),
"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
ClassPathResource
(
"data/customerWindows.csv"
).
getInputStream
(),
"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'
);
}
}
project-dispatch/src/main/java/com/dituhui/pea/dispatch/utils/DataUtils.java
0 → 100644
View file @
1a09ae8
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
;
}
}
project-dispatch/src/main/java/com/dituhui/pea/dispatch/utils/DispatchSolutionUtils.java
0 → 100644
View file @
1a09ae8
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
));
}
}
project-dispatch/src/main/resources/application.yaml
View file @
1a09ae8
...
...
@@ -26,7 +26,7 @@ spring:
enabled
:
false
datasource
:
driver-class-name
:
com.mysql.cj.jdbc.Driver
url
:
jdbc:mysql://10.10.0.
116
:3306/saas_aftersale_test?serverTimezone=Asia/Shanghai
url
:
jdbc:mysql://10.10.0.
54
:3306/saas_aftersale_test?serverTimezone=Asia/Shanghai
username
:
root
password
:
123456
type
:
com.alibaba.druid.pool.DruidDataSource
...
...
project-dispatch/src/main/resources/data/customerIndexXY.csv
0 → 100644
View file @
1a09ae8
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"
project-dispatch/src/main/resources/data/dispatchMap.html
0 → 100644
View file @
1a09ae8
<!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
project-dispatch/src/test/java/com/dituhui/pea/dispatch/test/Test.java
0 → 100644
View file @
1a09ae8
package
com
.
dituhui
.
pea
.
dispatch
.
test
;
import
java.io.FileNotFoundException
;
import
java.io.UncheckedIOException
;
import
org.optaplanner.core.api.solver.Solver
;
import
org.optaplanner.core.api.solver.SolverFactory
;
import
org.optaplanner.core.impl.solver.DefaultSolverFactory
;
import
com.dituhui.pea.dispatch.eventListener.DispatchSolverEventListener
;
import
com.dituhui.pea.dispatch.pojo.DispatchSolution
;
import
com.dituhui.pea.dispatch.utils.DataUtils
;
import
com.dituhui.pea.dispatch.utils.DispatchSolutionUtils
;
public
class
Test
{
public
static
void
main
(
String
[]
args
)
throws
UncheckedIOException
,
FileNotFoundException
{
// 创建解决方案对象
DispatchSolution
problem
=
DataUtils
.
getInitialProblem
();
// 创建求解器
DefaultSolverFactory
<
DispatchSolution
>
solverFactory
=
DispatchSolutionUtils
.
getSolverFactory
(
3
,
30
);
Solver
<
DispatchSolution
>
solver
=
solverFactory
.
buildSolver
();
// 得分监听器
solver
.
addEventListener
(
new
DispatchSolverEventListener
());
// 求解
DispatchSolution
solution
=
solver
.
solve
(
problem
);
// 打印和输出solution
DispatchSolutionUtils
.
printSolution
(
solution
);
// DispatchSolutionUtils.explainSolutionConstraintDetail(solution);
// DispatchSolutionUtils.exportSolutionJson(solution, "dispatchSolution.json");
// DispatchSolutionUtils.exportMapHtml(solution, "dispatchMap" +
// System.currentTimeMillis());
// FIXME
DispatchSolution
movedSolution
=
DispatchSolutionUtils
.
moveCustomer
(
solverFactory
,
solution
,
solution
.
getTechnicianList
().
get
(
0
),
0
,
1
);
DispatchSolutionUtils
.
printSolution
(
movedSolution
);
}
}
project-order/src/main/resources/application.yaml
View file @
1a09ae8
...
...
@@ -26,7 +26,7 @@ spring:
# - optional:nacos:datasource-config.yaml
datasource
:
driver-class-name
:
com.mysql.cj.jdbc.Driver
url
:
jdbc:mysql://10.10.0.
116
:3306/saas_aftersale_test?serverTimezone=Asia/Shanghai
url
:
jdbc:mysql://10.10.0.
54
:3306/saas_aftersale_test?serverTimezone=Asia/Shanghai
username
:
root
password
:
123456
type
:
com.alibaba.druid.pool.DruidDataSource
...
...
Write
Preview
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment