Skip to content

Commit 295adc4

Browse files
committed
Moving to more realistic routing example
1 parent fdeb544 commit 295adc4

File tree

7 files changed

+252
-107
lines changed

7 files changed

+252
-107
lines changed

java-ortools-routing/input.json

Lines changed: 112 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,115 @@
11
{
2-
"distance_matrix": [
3-
[
4-
0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468,
5-
776, 662
6-
],
7-
[
8-
548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016,
9-
868, 1210
10-
],
11-
[
12-
776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130,
13-
788, 1552, 754
14-
],
15-
[
16-
696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164,
17-
560, 1358
18-
],
19-
[
20-
582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050,
21-
674, 1244
22-
],
23-
[
24-
274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514,
25-
1050, 708
26-
],
27-
[
28-
502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514,
29-
1278, 480
30-
],
31-
[
32-
194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662,
33-
742, 856
34-
],
35-
[
36-
308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320,
37-
1084, 514
38-
],
39-
[
40-
194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274,
41-
810, 468
42-
],
43-
[
44-
536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730,
45-
388, 1152, 354
46-
],
47-
[
48-
502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650,
49-
274, 844
50-
],
51-
[
52-
388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536,
53-
388, 730
54-
],
55-
[
56-
354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342,
57-
422, 536
58-
],
59-
[
60-
468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342,
61-
0, 764, 194
62-
],
63-
[
64-
776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422,
65-
764, 0, 798
66-
],
67-
[
68-
662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536,
69-
194, 798, 0
70-
]
2+
"defaults": {
3+
"vehicles": {
4+
"start_location": {
5+
"lon": 7.625,
6+
"lat": 51.9622
7+
},
8+
"end_location": {
9+
"lon": 7.625,
10+
"lat": 51.9622
11+
},
12+
"start_time": "2024-03-19T11:00:00+00:00",
13+
"capacity": 5
14+
},
15+
"stops": {
16+
"quantity": 1
17+
}
18+
},
19+
"vehicles": [
20+
{
21+
"id": "vehicle-1"
22+
},
23+
{
24+
"id": "vehicle-2"
25+
}
7126
],
72-
"num_vehicles": 4,
73-
"depot": 0
27+
"stops": [
28+
{
29+
"id": "location-1",
30+
"location": {
31+
"lon": 7.6228,
32+
"lat": 51.9624
33+
}
34+
},
35+
{
36+
"id": "location-2",
37+
"location": {
38+
"lon": 7.6353,
39+
"lat": 51.9584
40+
}
41+
},
42+
{
43+
"id": "location-3",
44+
"location": {
45+
"lon": 7.6233,
46+
"lat": 51.9638
47+
}
48+
},
49+
{
50+
"id": "location-4",
51+
"location": {
52+
"lon": 7.6163,
53+
"lat": 51.9635
54+
}
55+
},
56+
{
57+
"id": "location-5",
58+
"location": {
59+
"lon": 7.6045,
60+
"lat": 51.9509
61+
}
62+
},
63+
{
64+
"id": "location-6",
65+
"location": {
66+
"lon": 7.6292,
67+
"lat": 51.9577
68+
}
69+
},
70+
{
71+
"id": "location-7",
72+
"location": {
73+
"lon": 7.6385,
74+
"lat": 51.9694
75+
}
76+
},
77+
{
78+
"id": "location-8",
79+
"location": {
80+
"lon": 7.6628,
81+
"lat": 51.9616
82+
}
83+
},
84+
{
85+
"id": "location-9",
86+
"location": {
87+
"lon": 7.6217,
88+
"lat": 51.9667
89+
}
90+
},
91+
{
92+
"id": "location-10",
93+
"location": {
94+
"lon": 7.6299,
95+
"lat": 51.9582
96+
}
97+
}
98+
],
99+
"duration_matrix": [
100+
[0, 831, 968, 648, 773, 689, 872, 997, 685, 698, 613, 613, 613, 613],
101+
[822, 0, 415, 257, 273, 83, 152, 266, 212, 74, 262, 262, 262, 262],
102+
[925, 466, 0, 342, 499, 454, 447, 624, 294, 463, 372, 372, 372, 372],
103+
[647, 293, 456, 0, 207, 168, 275, 452, 88, 178, 101, 101, 101, 101],
104+
[775, 385, 584, 202, 0, 280, 387, 544, 238, 289, 229, 229, 229, 229],
105+
[739, 143, 455, 211, 228, 0, 191, 308, 249, 9, 179, 179, 179, 179],
106+
[843, 152, 415, 260, 348, 158, 0, 274, 201, 149, 297, 297, 297, 297],
107+
[1001, 270, 573, 418, 466, 276, 293, 0, 370, 267, 455, 455, 455, 455],
108+
[687, 217, 444, 89, 247, 208, 199, 375, 0, 214, 141, 141, 141, 141],
109+
[748, 134, 446, 203, 219, 9, 182, 299, 240, 0, 188, 188, 188, 188],
110+
[613, 272, 372, 102, 221, 129, 320, 437, 139, 138, 0, 0, 0, 0],
111+
[613, 272, 372, 102, 221, 129, 320, 437, 139, 138, 0, 0, 0, 0],
112+
[613, 272, 372, 102, 221, 129, 320, 437, 139, 138, 0, 0, 0, 0],
113+
[613, 272, 372, 102, 221, 129, 320, 437, 139, 138, 0, 0, 0, 0]
114+
]
74115
}

java-ortools-routing/java-ortools.code-workspace

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@
1818
},
1919
"extensions": {
2020
"recommendations": ["ms-vscode-remote.remote-containers"]
21-
}
21+
},
22+
"settings": {}
2223
}

java-ortools-routing/src/main/java/com/nextmv/example/Input.java

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,69 @@
22

33
import java.io.BufferedReader;
44
import java.io.InputStreamReader;
5+
import java.util.List;
56
import java.util.stream.Collectors;
67

78
import com.google.gson.Gson;
89
import com.google.gson.annotations.SerializedName;
910

1011
public class Input {
11-
@SerializedName(value = "distance_matrix")
12-
public final long[][] distanceMatrix;
13-
@SerializedName(value = "num_vehicles")
14-
public final int vehicleNumber;
15-
public final int depot;
12+
public final class Location {
13+
public double lat;
14+
public double lon;
1615

17-
public Input(int vehicleNumber, int depot, long[][] distanceMatrix) {
18-
this.distanceMatrix = new long[vehicleNumber+1][vehicleNumber+1];
19-
this.vehicleNumber = vehicleNumber;
20-
this.depot = depot;
21-
for (int i = 0; i < vehicleNumber+1; i++) {
22-
for (int j = 0; j < vehicleNumber+1; j++) {
23-
this.distanceMatrix[i][j] = distanceMatrix[i][j];
24-
}
16+
public Location(double lat, double lon) {
17+
this.lat = lat;
18+
this.lon = lon;
2519
}
2620
}
2721

28-
public long[][] getDistanceMatrix() {
29-
return this.distanceMatrix;
22+
public final class Vehicle {
23+
public String id;
24+
25+
public Vehicle(String id) {
26+
this.id = id;
27+
}
3028
}
3129

32-
public int getVehicleNumber() {
33-
return this.vehicleNumber;
30+
public final class Stop {
31+
public String id;
32+
public Location location;
3433
}
3534

36-
public int getDepot() {
37-
return this.depot;
35+
@SerializedName(value = "duration_matrix")
36+
public final long[][] durationMatrix;
37+
public final List<Vehicle> vehicles;
38+
public final List<Stop> stops;
39+
40+
public Input(
41+
long[][] durationMatrix,
42+
List<Vehicle> vehicles,
43+
List<Stop> stops) {
44+
if (durationMatrix != null) {
45+
for (long[] row : durationMatrix) {
46+
if (row.length != durationMatrix.length) {
47+
throw new IllegalArgumentException("Duration matrix must be square.");
48+
}
49+
}
50+
if (durationMatrix.length != stops.size() * 2 * vehicles.size()) {
51+
throw new IllegalArgumentException(
52+
"Duration matrix size does not match number of stops and vehicles (n_stops + 2 * n_vehicles).");
53+
}
54+
}
55+
56+
this.durationMatrix = durationMatrix;
57+
this.vehicles = vehicles;
58+
this.stops = stops;
3859
}
39-
60+
4061
public static Input fromString(String path) {
4162
Gson gson = new Gson();
4263
// Read stdin if no path is provided.
4364
if (path.isEmpty()) {
4465
try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
4566
return gson.fromJson(
46-
reader.lines().collect(Collectors.joining("\n")), Input.class
47-
);
67+
reader.lines().collect(Collectors.joining("\n")), Input.class);
4868
} catch (java.io.IOException e) {
4969
System.err.println("Error reading stdin: " + e.getMessage());
5070
System.exit(1);
@@ -54,9 +74,8 @@ public static Input fromString(String path) {
5474
// Read the path otherwise.
5575
try {
5676
return gson.fromJson(
57-
java.nio.file.Files.readString(java.nio.file.Paths.get(path)),
58-
Input.class
59-
);
77+
java.nio.file.Files.readString(java.nio.file.Paths.get(path)),
78+
Input.class);
6079
} catch (java.io.IOException e) {
6180
System.err.println("Error reading '" + path + "': " + e.getMessage());
6281
System.exit(1);

java-ortools-routing/src/main/java/com/nextmv/example/Main.java

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,45 @@ public static void main(String[] args) {
2525

2626
Loader.loadNativeLibraries();
2727

28+
// Generate all vehicle start / end indices.
29+
int[] startIndices = new int[input.vehicles.size()];
30+
int[] endIndices = new int[input.vehicles.size()];
31+
for (int i = 0; i < input.vehicles.size(); ++i) {
32+
startIndices[i] = input.stops.size() + i * 2;
33+
endIndices[i] = input.stops.size() + i * 2 + 1;
34+
}
35+
2836
// Create Routing Index Manager.
29-
RoutingIndexManager manager = new RoutingIndexManager(input.distanceMatrix.length, input.vehicleNumber,
30-
input.depot);
37+
RoutingIndexManager manager = new RoutingIndexManager(
38+
input.stops.size() + 2 * input.vehicles.size(),
39+
input.vehicles.size(),
40+
startIndices,
41+
endIndices);
3142

3243
// Create Routing Model.
3344
RoutingModel routing = new RoutingModel(manager);
3445

3546
// Create and register a transit callback.
36-
final int transitCallbackIndex = routing.registerTransitCallback((long fromIndex, long toIndex) -> {
37-
// Convert from routing variable Index to user NodeIndex.
38-
int fromNode = manager.indexToNode(fromIndex);
39-
int toNode = manager.indexToNode(toIndex);
40-
return input.distanceMatrix[fromNode][toNode];
41-
});
47+
int transitCallbackIndex;
48+
if (input.durationMatrix != null) {
49+
// If durationMatrix is provided, use it.
50+
transitCallbackIndex = routing.registerTransitCallback((long fromIndex, long toIndex) -> {
51+
int fromNode = manager.indexToNode(fromIndex);
52+
int toNode = manager.indexToNode(toIndex);
53+
return input.durationMatrix[fromNode][toNode];
54+
});
55+
} else {
56+
// If durationMatrix is not provided, use haversine distance.
57+
transitCallbackIndex = routing.registerTransitCallback((long fromIndex, long toIndex) -> {
58+
int fromNode = manager.indexToNode(fromIndex);
59+
int toNode = manager.indexToNode(toIndex);
60+
return (long) haversine(
61+
input.stops.get(fromNode).location.lat,
62+
input.stops.get(fromNode).location.lon,
63+
input.stops.get(toNode).location.lat,
64+
input.stops.get(toNode).location.lon);
65+
});
66+
}
4267

4368
// Define cost of each arc.
4469
routing.setArcCostEvaluatorOfAllVehicles(transitCallbackIndex);
@@ -72,7 +97,7 @@ static Output getOutput(
7297
Assignment solution, long solveStartTime) {
7398
long maxRouteDistance = 0;
7499
List<Vehicle> vehicles = new ArrayList<Vehicle>();
75-
for (int i = 0; i < input.vehicleNumber; ++i) {
100+
for (int i = 0; i < input.vehicles.size(); ++i) {
76101
List<Integer> stops = new ArrayList<Integer>();
77102
long index = routing.start(i);
78103
long routeDistance = 0;
@@ -106,4 +131,25 @@ static Output getOutput(
106131
duration,
107132
runDuration);
108133
}
134+
135+
/**
136+
* Haversine formula to calculate the distance between two points on the Earth
137+
* given their latitude and longitude.
138+
*
139+
* @param lat1 latitude of the first point
140+
* @param lon1 longitude of the first point
141+
* @param lat2 latitude of the second point
142+
* @param lon2 longitude of the second point
143+
* @return the distance in kilometers between the two points
144+
*/
145+
private static double haversine(double lat1, double lon1, double lat2, double lon2) {
146+
final double R = 6371; // Radius of the Earth in kilometers
147+
double latDistance = Math.toRadians(lat2 - lat1);
148+
double lonDistance = Math.toRadians(lon2 - lon1);
149+
double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2) +
150+
Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
151+
Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2);
152+
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
153+
return R * c;
154+
}
109155
}

0 commit comments

Comments
 (0)