4 Basic planning walkthrough

In the following sections, we walkthrough basic examples of key ODL Live usage cases primarily using the main realtime endpoint, examining more advanced topics as and when they arise.

You can run optimisation models on the main realtime endpoint, the command line and the static queue. After you run the model detailed below on the main endpoint, you may also want to try running it on the command line.

4.1 Connecting to ODL Live

If you’ve followed the separate installation guide to install ODL Live, you should now have:

  1. A base URL for your system. This must be HTTPS, not HTTP, to be secure.

  2. A username for the REST HTTP calls.

  3. A password for the REST HTTP calls.

The authentication is stateless - you add the username and password to the header of each HTTP request you send. As the server is HTTPS, this username and password is encrypted within the HTTP request (all data in the request is encrypted). You should connect to the server using Basic Authentication. To enable Basic Authentication, you concatenate the username and password together separated by a ‘:’, so username MyUser and password MyPassword are concatenated as MyUser:MyPassword. You then encode this as a Base64 string and add it to the Authorization HTTP header field, e.g.

Authorization: Basic TXlVc2VyOk15UGFzc3dvcmQ=

4.1.1 First connection to main endpoint walkthrough

Postman is a useful free tool for testing REST endpoints which can be downloaded from here. Download, install and then open Postman. Using Postman, (a) create a new query, (b) select Basic Authentication (under the Authorization tab), (c) enter the username and password, (d) click the button to Update Request and then (e) view the resulting Authentication header under the headers tab. Now change the HTTP method type in the Postman query to GET (if it’s not already) and the URL to the following one:

my-base-URL/models/

where you replace my-base-URL with the one provided to you, e.g.  if your base-URL was https://example.odllive.com you should GET from:

https://example.odllive.com/models

Finally press Send to send the request. If the request was successful, you should receive a 200 OK HTTP response and an empty JSON array:

[]

If your version of ODL Live already contains some example model data, you will see these models in the JSON array.

4.2 Real-world time affects the model!!

Warning. ODL Live uses the current real-world time (in UTC) as part of its calculations, because for realtime problems we need to use the current time to estimate when a vehicle can arrive at or complete a job. The example JSON data in the next sections contains dates set in the future. By default ODL Live will not schedule data in the past unless you override the current time. If you override the current time, ODL Live will use this override when planning routes for a model. To do this, we set the timeOverride object, within the model model.data.configuration object. The following JSON shows a model object with the timeOverride set to 00:00 UTC on the 1st of January 2000:

{
  "data" : {
    "jobs" : [ 
        ... job data lives here
    ],
    "vehicles" : [
        ... vehicle data lives here
    ]
  },
  "configuration" : {
    "distances" : {
        ... distance calculation configuration lives here
    },
    "timeOverride" : {
      "override" : "2000-01-01T00:00",
      "overrideType" : "SCHEDULER"
    }
  },
  "_id" : "myModelId1"
}

model.data.configuration.timeOverride.overrideType should always be set to “SCHEDULER”. You should never set timeOverride in realtime models (including models where you just want to get updated ETAs for routes you’re currently running live), but should probably set timeOverride on all other types of models.

You can also automatically set the current time based on the earliest vehicle opening time, as follows:

  "timeOverride" : {
    "overrideType" : "SCHEDULER",
    "useEarliestVehicleOpeningTime" : true
  }

4.3 PUT a model to main endpoint

The following JSON defines a very simple model with one job and one vehicle:

{
  "data" : {
    "jobs" : [ {
      "stops" : [ {
        "type" : "SERVICE",
        "coordinate" : { "latitude" : 51.5074, "longitude" : -0.1001 },
        "openTime" : "2099-01-01T09:00",
        "lateTime" : "2099-01-01T17:00",
        "closeTime" : "2099-01-02T17:00",
        "durationMillis" : 3600000,
        "_id" : "TateModern1"
      } ],
      "_id" : "TateModern1"
    } ],
    "vehicles" : [ {
      "definition" : {
        "start" : {
          "type" : "START_AT_DEPOT",
          "coordinate" : { "latitude" : 51.5416, "longitude" : -0.1462 },
          "openTime" : "2099-01-01T08:00" },
        "end" : {
          "type" : "RETURN_TO_DEPOT",
          "coordinate" : { "latitude" : 51.5416, "longitude" : -0.1462 },
          "lateTime" : "2099-01-01T18:00",
          "closeTime" : "2099-01-02T18:00" },
        "costPerTravelHour" : 1.0,
        "costPerWaitingHour" : 0.5,
        "costPerServicingHour" : 1.0,
        "costPerKm" : 1.0E-6,
        "costFixed" : 100.0,
        "costPerStop" : 0.0 },
      "_id" : "Camden1"
    } ] },
  "configuration" : {
    "distances" : {
      "roadNetworkTimeMultiplier" : 1.0,
      "useRoadNetwork" : false,
      "straightLineSpeedMetresPerSec" : 22.352,
      "straightLineDistanceMultiplier" : 1.0 }
  }
}

A model is an object of type ODLModel and it contains two key objects - the data object of type ODLModelData (containing jobs and vehicles) and the configuration object of type ODLModelConfiguration. Generally speaking, the recommended JSON for ODLModelConfiguration should be supplied to you, so you don’t need to worry too much about the configuration object.

The ODLModelData object contains an array of jobs, of type ODLJob and an array of vehicles, of type ODLVehicle. An ODLJob has an array of stops, containing either one or two stops, of type ODLStop. Our example job has one stop of stoptype SERVICE with a latitude and longitude that places it at the Tate Modern gallery in Central London, UK. The job has an id TateModern1 and the stop has an id TateModern1. The earliest arrival time at the stop is set to 09:00 on 2099-01-01 and the late time to the end of the same working day. The optimiser will therefore strongly penalise serving this stop after the end of the day. The close time is 24 hours later, which effectively turns off the hard end time window (this is the recommended configuration). The optimiser will not allow the stop to be served after the end time. Stop and job ids can be identical or different; each stop id and each job id must however be unique within its object type. You can use the primary key ids from your own ordering system for the jobs, vehicles and other objects, providing they are URL-friendly.

Our example ODLVehicle has an id of Camden1, stored on its _id field. A vehicle has a definition object of type ODLVehicleDefinition which stores, amongst other things, ODLStop objects defining the vehicle’s start and end. Not all ODLStop fields are used within the vehicle’s start and end. Only openTime, coordinate and the type (set to START_AT_DEPOT) should be set on the start. Only lateTime, closeTime, coordinate and the type (set to RETURN_TO_DEPOT) should be set on the end. The latitudes and longitudes on this vehicle’s start and end stops place it in North London, UK. The start and end latitudes and longitudes can be the same or different. The definition object also stores various cost values for the vehicle - see the cost model section for more details. For real-time modelling (covered in later sections), the vehicle object will also store its history.

You may notice that all dates on both the job and vehicle are set in the future. ODL Live is setup for realtime modelling and will not optimise data with dates set in the past unless you override the current time.

As mentioned above, we will not cover much detail on the configuration object however it is important to note that this example configuration object will use Euclidean (straight line) distances for distance and travel time calculation. Different ODL Live installations will be setup with different road network data files for different countries. To ensure that the walkthrough examples presented here work anywhere on any ODL Live installation, they use only Euclidean distances and therefore do not require country-specific road network data. Your own configuration will be configured differently, for road network distances in your country of interest.

We will now PUT this entire model to ODL Live. Use the Postman tool to create a new PUT request and set the username and password in the header as described in the section on connecting to ODL Live. Set the URL for the PUT to:

my-base-URL/models/TestModel1

For example, if your base URL is https://example.odllive.com then the PUT URL should be:

https://example.odllive.com/models/TestModel1

This will PUT the model using the id TestModel1. Now set the body of the request to be of type ‘raw’ and in the dropdown that appears on the right when you select raw, select type JSON (application/json). Then copy and paste the example JSON above for our model into the text box for the request body data, as per the following image:

 

Copying and pasting the JSON might destroy the formatting (i.e. indentations) but the text will still be valid JSON. Press Send in Postman and a second or so later you should have a 200 OK response if the request was configured correctly. You’ve now sent the model to ODL Live main endpoint, where it will keep on optimising until you delete it.

You can also run models on the static queue or command line API. You would use the same model JSON for running on the main endpoint, static queue or command line however with the command line, you would save this model JSON to a text file, which the command line then reads in.

To confirm the model data is now present on the main endpoint of ODL Live server, change the type of the request to GET but keep the URL unchanged as:

my-base-URL/models/TestModel1

Press Send to send the GET and your response should return the JSON for the model, including some additional fields and data added by default by ODL Live. Any data you send to ODL Live should always return as sent in the GET, however more fields may be added.

4.4 View routes on map

Although ODL Live is primarily an engine, it includes a basic dashboard to help software developers with integration, which you can use to view maps. This dashboard is available on the URL:

my-base-URL/experimental/dashboard

After logging into the dashboard you should see a table of models including your new model TestModel1. Click on the view map link and you should see a map similar to the following image (assuming the data was entered correctly):

 

Road network distances are not enabled in this model configuration, so we see a straight line between the vehicle’s start/end locations and the stop.

4.5 GET planned routes from main endpoint

In Postman, ensure your request is still GET and has the correct basic authentication header. Change the URL to:

my-base-URL/models/TestModel1/optimiserstate/plan

Press Send. Until an optimisation burst has run, you will receive either a 404 NOT FOUND, an empty JSON object or a ‘plan’ with all jobs unplanned. You may therefore need to press Send once or twice more until a second or two has passed and you get the resulting plan. If you already have many models running on your server, this may take longer. Once a burst has been run, you should get an ODLOptimiserPlan object returned like the following JSON (but also containing some additional data that we will discuss in later sections):

{
  "vehiclePlans" : [ {
    "vehicleId" : "Camden1",
    "plannedStops" : [ {
      "stopId" : "TateModern1",
      "timeEstimates" : {
        "arrival" : "2099-01-01T08:03:42.048944792",
        "start" : "2099-01-01T08:59:59.999999992",
        "complete" : "2099-01-01T09:59:59.999999992"
      },
      "earliestDispatch" : "2099-01-01T08:26:17.951"
    } ]
  } ]
}

This is the very first plan that ODL Live returns for your model and because your model is tiny, it will be the best plan ODL Live finds. For most models, you will get a much better plan if you let the model run for longer on ODL Live (i.e. the optimiser will find much better plans after the first one). Models running on the main endpoint actually carry on optimising forever until you delete them.

If you’re running your model on the command line instead of the main endpoint, the command line would output this same plan JSON but as a text file saved to your file system. With the command line (and the static queue) you can also set detailed criteria for when ODL Live should stop optimising a model.

When you inspect the returned JSON in Postman, ensure you have the Pretty tab selected so the JSON is formatted properly with syntax highlighting, otherwise it will be difficult to read. The ODLOptimiserPlan object has an array of ODLVehiclePlan objects called vehiclePlans, which should contain a single vehicle plan. The ODLVehiclePlan object stores the id of the vehicle (corresponding to the id of the single ODLVehicle in our model JSON). The ODLVehiclePlan object also has an array of ODLPlannedStop objects called plannedStops, which should contain our single planned stop. The ODLPlannedStop object stores the id of the stop, corresponding to the ODLStop object within the ODLJob object, in our model JSON.

The plan also stores other data such as arrival time estimates and various statistics, which you will see in the returned JSON. As ODL Live minimises its data transfer to the database, only the stop ids within a ODLVehiclePlan vehicle plan are persisted, the other values are recreated. The upshot of this is that if the virtual server hosting ODL Live has been rebooted (e.g. on an Amazon Webservices update), you may see, for a few seconds, that only the stop ids are available but not the other fields. If you require the other fields simply keep on repolling every couple of seconds.

4.5.1 Commas separated route sheet

A comma separated text file of the pending stops in a model’s plan is available to download at:

my-base-URL/models/TestModel1/optimiserstate/textroutesheet

The following is a sample output from this sheet:

"Vehicle","JobId","StopId","StopType","ArriveTime","StartTime","CompleteTime","Latitude","Longitude"
"Veh1","","","Start",00:00:00,00:00:00,00:00:00,51.5138,-0.0984
"Veh1","Job4","Stop4","Deliver",00:03:36,00:03:36,00:03:36,51.4831,-0.1482
"Veh1","Job65","Stop65","Deliver",00:04:05,00:04:05,00:04:05,51.4788,-0.1541
"Veh1","Job24","Stop24","Deliver",00:04:43,00:04:43,00:04:43,51.4712,-0.1565
"Veh1","Job25","Stop25","Deliver",00:05:29,00:05:29,00:05:29,51.4775,-0.1457
"Veh1","","","End",00:17:35,00:17:35,00:17:35,51.5138,-0.0984
"Veh2","","","Start",00:00:00,00:00:00,00:00:00,51.5138,-0.0984
....

StopType is designed to be read by a human user and is therefore more user-friendly than the type enumeration used in the ODL Stop object. It’s values can be “Deliver”, “Collect”, “Service”, “Pickup”, “Dropoff”, “Start”, “Break”, “End”.

More fields may be added to the text route sheet in the future and the order of columns may change. If you use the text route sheet as the basis for an import process into your systems, you should therefore select and match data based on the column names in the header line. You should not assume the indexes for each column are fixed.

4.6 PUT and DELETE jobs and vehicles using main endpoint

4.6.1 PUT job

We now add another job object (of type ODLJob) to our solution. Change the request method type in Postman back to PUT, change the URL to:

my-base-URL/models/TestModel1/jobs/MadameTussauds1

and add the following JSON to the body:

{
  "stops" : [ {
    "type" : "SERVICE",
    "coordinate" : {
      "latitude" : 51.5229,
      "longitude" : -0.155
    },
    "openTime" : "2099-01-01T09:00",
    "lateTime" : "2099-01-01T13:00",
    "closeTime" : "2099-01-02T17:00",
    "durationMillis" : 3600000,
    "costFixed" : 0.0,
    "_id" : "MadameTussauds1"
  } ]
}

We have not included the id of this new job object in the JSON (although the stop id is included), as the job id MadameTussauds1 is included in the URL itself, following a RESTful design. Press Send and you should get a 200 OK response.

If you repoll for the plan using

GET my-base-URL/models/TestModel1/optimiserstate/plan

again repolling until it changes a couple of seconds later, you should see both stops are now planned.

4.6.2 DELETE vehicle

Change the request method type in Postman to DELETE and change the URL to:

my-base-URL/models/TestModel1/vehicles/Camden1

Press Send and you should get a 200 OK response. GET the plan, repolling as needed until its updated, and you should see that no routes are planned as no vehicles are available. The unplannedJobs array in the plan should contain the JSON for both your jobs.

4.6.3 PUT vehicle

We now PUT another vehicle (of type ODLVehicle) back onto the system so we can assign our jobs again. Change the request method type in Postman to PUT and change the URL to:

my-base-URL/models/TestModel1/vehicles/NewVehicle2

Set the request body to the following JSON for the ODLVehicle. Again, we omit the id field as we send it in the PUT URL.

{
  "definition" : {
    "start" : {
      "type" : "START_AT_DEPOT",
      "coordinate" : { "latitude" : 51.56026, "longitude" : -0.16067 },
      "openTime" : "2099-01-01T09:00" },
    "end" : {
      "type" : "RETURN_TO_DEPOT",
      "coordinate" : { "latitude" : 51.56026, "longitude" : -0.16067 },
      "lateTime" : "2099-01-01T17:00",
      "closeTime" : "2099-01-02T18:00" },
    "costPerTravelHour" : 1.0,
    "costPerWaitingHour" : 0.5,
    "costPerServicingHour" : 1.0,
    "costPerKm" : 1.0E-6,
    "costFixed" : 100.0,
    "costPerStop" : 0.0
  }
}

Now press Send to PUT the new vehicle, change your method type back to GET and your URL to

my-base-URL/models/TestModel1/optimiserstate/plan

repolling as needed and you should see all stops loaded again. You can use PUT to send updated versions of existing objects, as well as new objects.

4.7 DELETE model

To clean up afterwards we should delete our test model. Models will use CPU resource and memory when they’re on the system, so you should only keep models which you’re actually using. To delete the whole model, change the method type to DELETE and the URL to:

my-base-URL/models/TestModel1

Now press Send and you should receive a 200 OK. If you change the method type to GET and press Send you should now receive a 404 NOT FOUND as the model no longer exists and if you change the URL to

my-base-URL/models/

and press Send the list of models should no longer contain our example model with id TestModel1.