1

I'm working on a project that uses this example code at its core; using a custom distance/time matrix to solve the routing problem. The algorithm appears to work perfectly but I cannot find a way to view the outputs in a graph. Running just the example code without any modification results in:

2015-07-07 11:56:33,354 [main] WARN jsprit.analysis.toolbox.Plotter - cannot plot vrp, since coord is missing

This error makes sense. Nothing can be plotted because no locations are actually specified; we have only specified the relative distance/time between locations. I have to use a custom matrix because I'm working in lat/long and need the real road distance between points. However, as my problem scales up, it would be of great use to me simply to assign each location with its lat/long and to produce a graph treating these points as though they were cartesian coordinates. The catchment area isn't huge so it should still let me quickly see if a solution makes sense without having to do more elaborate plots. So my question is whether there's a simple way of getting jsprit to solve the problem using a custom distance/time matrix but assigning coordinates to the locations for the purpose of plotting? I can't seem to figure it out, thanks in advance.

EDIT: I have spent a long time working on this to no avail, even with the proposed changes from Stefan. I cannot find a way to do it without changing the base code and I don't want that to cause knock-on problems to what I already have working.

Building a location is fine, though it's slightly different than the proposed code:

Location.Builder.newInstance().setId("0").setCoordinate(Coordinate.newInstance(10.0, 10.0)).build();

The problem then comes that when I wish to make a Service, I am forced by the current code to define a new location (Location only accepts a newInstance):

Service s2 = Service.Builder.newInstance("2").addSizeDimension(0, 1).setLocation(Location.newInstance(6.0, 1.0)).build();

I cannot find an existing way to simply say that the Service is at a pre-defined location.

Moving on, I thought about adding the locations directly into VehicleRoutingProblem.Builder. Note that adding a location to the builder requires it to be defined as "addLocation(String LocationID, Coordinate coordinate), so it won't take pre-defined locations explicitly; they must be defined within the Builder. This looks like the following:

    VehicleType type = VehicleTypeImpl.Builder.newInstance("type").addCapacityDimension(0, 5).setCostPerDistance(1).setCostPerTime(2).build();
    VehicleImpl vehicle = VehicleImpl.Builder.newInstance("vehicle")
            .setStartLocation(Location.newInstance("0")).setType(type).build();

    Service s1 = Service.Builder.newInstance("1").addSizeDimension(0, 1).setLocation(Location.newInstance("1")).build();
    Service s2 = Service.Builder.newInstance("2").addSizeDimension(0, 1).setLocation(Location.newInstance("2")).build();
    Service s3 = Service.Builder.newInstance("3").addSizeDimension(0, 1).setLocation(Location.newInstance("3")).build();        

    //define a matrix-builder building an asymmetric matrix
    VehicleRoutingTransportCostsMatrix.Builder costMatrixBuilder = VehicleRoutingTransportCostsMatrix.Builder.newInstance(true);
    costMatrixBuilder.addTransportDistance("0", "1", 19.13);
    costMatrixBuilder.addTransportDistance("0", "2", 18.56);
    costMatrixBuilder.addTransportDistance("0", "3", 21.68);
    costMatrixBuilder.addTransportDistance("1", "0", 15.91);
    costMatrixBuilder.addTransportDistance("1", "2", 15.01);
    costMatrixBuilder.addTransportDistance("1", "3", 11.45);
    costMatrixBuilder.addTransportDistance("2", "0", 19.42);
    costMatrixBuilder.addTransportDistance("2", "1", 12.54);
    costMatrixBuilder.addTransportDistance("2", "3", 11.13);
    costMatrixBuilder.addTransportDistance("3", "0", 25.75);
    costMatrixBuilder.addTransportDistance("3", "1", 9.94);
    costMatrixBuilder.addTransportDistance("3", "2", 11.24);

    costMatrixBuilder.addTransportTime("0", "1", 12);
    costMatrixBuilder.addTransportTime("0", "2", 11);
    costMatrixBuilder.addTransportTime("0", "3", 15);
    costMatrixBuilder.addTransportTime("1", "0", 10);
    costMatrixBuilder.addTransportTime("1", "2", 10);
    costMatrixBuilder.addTransportTime("1", "3", 10);
    costMatrixBuilder.addTransportTime("2", "0", 15);
    costMatrixBuilder.addTransportTime("2", "1", 9);
    costMatrixBuilder.addTransportTime("2", "3", 10);
    costMatrixBuilder.addTransportTime("3", "0", 17);
    costMatrixBuilder.addTransportTime("3", "1", 13);
    costMatrixBuilder.addTransportTime("3", "2", 10);

    VehicleRoutingTransportCosts costMatrix = costMatrixBuilder.build();

    VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance().setFleetSize(FleetSize.FINITE).setRoutingCost(costMatrix)
            .addVehicle(vehicle).addJob(s1).addJob(s2).addJob(s3)
            .addLocation("0", Coordinate.newInstance(1.0, 1.0)).addLocation("1", Coordinate.newInstance(9.0, 2.0))
            .addLocation("2", Coordinate.newInstance(5.0, 4.0)).addLocation("3", Coordinate.newInstance(4.0, 8.0))
            .addLocation("4", Coordinate.newInstance(3.0, 7.0)).build();`

That runs fine. But, it still won't plot because it doesn't make the association between the location indexes and the ability to plot them. However, I can confirm (as was requested) that the solver only uses the pre-defined costMatrix, so the printed answer is still correct.

roganjosh
  • 12,594
  • 4
  • 29
  • 46

1 Answers1

1

You can just assign lon/lat to your locations. If you set your own cost matrix, the algorithm should consider the cost matrix only, i.e. the coordinates should have no impact on the algorithm (if it has, please let me know). However, you should then be able to plot it, i.e. to treat coordinates as though they were cartesian.

Edit: Locations can be defined with a location factory and a builder. One of the 3 factories works as follows:

Location location = Location.newInstance(10,12);

The builder is more flexible but not as convenient as the factory and works like this:

Location location = Location.Builder.newInstance().setId("1").setIndex(1).setCoordinate(Coordinate.newInstance(10,12).build();

These ways you create locations and you can always assign these locations to vehicles and services like this:

Service service = Service.Builder.newInstance("s1").setLocation(location).build();

and similar for vehicles. If you require a location id for your cost matrix and coordinates for plotting, specify a location that has an id and a coordinate (see above) ... and do not add locations manually to the problem builder. Note that you need to do it for your vehicles AND your services.

What you can also do is to extend the plotter such that it can project lon/lat to any preferred projection and contribute your extension to jsprit :). This brings me to another solution, you can always project lon/lat to a projection of your choice and then assign these projected coordinates to your jsprit locations which might make your plot much more appealing.

Edit: Btw: If I were you I would use FastVehicleRoutingCostMatrix which is based on indeces rather than ids. As the name suggests, it is just faster since it uses arrays instead of maps.

Stefan Schröder
  • 1,037
  • 7
  • 13
  • Thanks for your quick response (and such a fantastic tool!). I will do some testing later on to confirm that the cost matrix overrides the specified location and let you know either way. With it omitted in the example code I wasn't sure of the priority. I'm seriously new to Java so not sure what progress I will make on nicer plots but if I do get anywhere, I will post it back to you. The fact I'm already up and running is only due to the great example codes you have provided :) – roganjosh Jul 07 '15 at 14:05
  • I just want to check that I understand your answer correctly as I have not been successful. Are you suggesting that for a single Service definition I can do `.setLocation(Location.newInstance(1,1)).setLocation(Location.newInstance("1"))` ? I can see three methods of identifying a location (coordinate, string, index). Although it automatically detects what I have entered as a location, defining it a second time with a different type of identity removes the first definition. – roganjosh Jul 07 '15 at 17:38
  • Additionally, costMatrixBuilder.addTransportDistance appears to only take (string, string, double). The strings come from the location instance not the builder instance, so it seems you're forced to define locations by a string if you want to use a custom cost matrix. Defining by a coordinate pair invokes `jsprit.core.util.EuclideanDistanceCalculator.calculateDistance`. Apologies if I have misinterpreted your answer. – roganjosh Jul 07 '15 at 18:22
  • If I were you, I would use the FastVehicleRoutingTransportCostsMatrix. For this to work you need to assign an index to your service/shipment location. For example, you specify a travel time from a location with index 1 to a location with index 2 as follows: – Stefan Schröder Jul 07 '15 at 23:14
  • fastVehicleRoutingTransportCostsMatrix.addTransportTime(locationIndexA, locationIndexB, departureTime(A)) – Stefan Schröder Jul 07 '15 at 23:16
  • 1
    You can define the corresponding location as follows: Location.Builder().newInstance().setIndex(locationIndexA).setCoordinate(x,y).build(). – Stefan Schröder Jul 08 '15 at 05:47
  • That makes a lot more sense, thanks. I'll try it out later today hopefully and report back. If I get it working, would you like me to send back the "fixed" CostMatrixExample.Java example? What's the best way of doing that? – roganjosh Jul 08 '15 at 07:40
  • Hi, apologies for the delay, I had to work on something else. I spent a long time today working on your suggestions and it doesn't have the output we had hoped - it still won't plot. I have updated my answer with just a few of the things I tried (I did LOTS). I included the edit simply for your reference, I don't expect you to have to keep working at this niche problem. Thanks for your suggestions and time. – roganjosh Jul 13 '15 at 17:16
  • I'm very grateful that you updated your answer, I will try again. I did try the "fast" matrix builder, which had the same effect. I was confused by the "departureTime" field, and it didn't seem to make any difference. Wholeheartedly, I can tell you that the only reason I moved from Python to Java (from scratch) was because there is nothing that I know of that can compete on speed with GraphHopper+jsprit. The Python equivalent takes 18sec to plot a route that GraphHopper does in 2ms, and you solve my problem in less than 1/10 the time of Python. Speed is not even in my peripheral vision atm :) – roganjosh Jul 13 '15 at 22:44
  • Great! Btw: Here (https://graphhopper.com/api/1/examples/#optimization) you can see how Graphhopper & jsprit work together live :). – Stefan Schröder Jul 14 '15 at 06:54