This how-to guide assumes you already completed the steps described in the 5-minute getting started experience. To test that the Nextmv CLI is correctly configured, you can optionally run the following command on your terminal. It will get some files that are necessary to work with the Nextmv Platform. You can see the expected output as well.
The Nextmv Software Development Kit (SDK) lets you automate any operational decision in a way that looks and feels like writing other code. It provides the guardrails to turn your data into automated decisions and test and deploy them into production environments.
Introduction
This guide will walk you through our routing-matrix-input
and measure-matrix
templates. Given that our app environment is self-contained and thus, has no access to outside resources while the app is running, all needed information needs to be passed within the input data. The goal of this guide is to enable you to use custom distance or travel time matrices in our app environment. To get the templates, simply run the following commands.
Let's start looking at the routing-matrix-input
first. You can check that all files are available in the newly created routing-matrix-input
folder. Running the tree
command, you should see the file structure.
README.md
gives a short introduction to the routing problem and shows you how to run the template.go.mod
andgo.sum
define a Go module and are used to manage dependencies, including the Nextmv SDK.input.json
describes the input data for a specific routing problem that is solved by the template.license
contains the Apache License 2.0 under which we distribute this template.main.go
contains the actual code of the Nextmv routing app.- The
routing-matrix-input.code-workspace
file should be used to open the template in Visual Studio Code. It is pre-configured for you so that you can run and debug the template without any further steps.
Now you can run the template with the Nextmv CLI, reading from the input.json
file and writing to an output.json
file. The following command shows you how to specify solver limits as well. You should obtain an output similar to the one shown.
Note that transient fields like timestamps, duration, versions, etc. are represented with dummy values due to their dynamic nature. I.e., every time the input is run or the version is bumped, these fields will have a different value.
Now we will show you, step by step, what the code inside the main.go
achieves.
Dissecting the routing app
The first part of the main.go
defines a package name, imports packages that are needed by the code below and a main
function which is the starting point for the app. In the main
function the Run
function from the Nextmv run
package is being called. This function executes a solver which is passed in the form of the solver
function further down in the file.
But before we look into the solver
function, we will examine the different structs that represent the needed data used by the app.
The Input
The input
struct lists the four required input fields, Stops
and Vehicles
, DurationMatrix
and DistanceMatrix
, as well as optional fields for Starts
and Ends
. Stops
describes the list of locations to visit, and Vehicles
is an array of vehicle IDs. Both matrices, DurationMatrix and DistanceMatrix represent the point to point costs in distance and travel time, respectively. The optional fields represent fixed start and end positions of the vehicles used.
The Solver
The solver
function is where the model is defined. The function's signature adheres to the run.Run
function we saw earlier already.
When you first ran the template you passed in the parameter -runner.input.path
followed by the path to an input file. This file is automatically parsed and converted to our input
struct. Other option arguments are also interpreted automatically passed to the solver as an Options
struct.
Similar to the options you passed in when you ran the template, -limits.duration
and -diagram.expansion.limit
you can also set these values directly in your main.go
if you'd prefer not to pass them from the command line.
The routing model itself is composable by options. Since the input file in this example contains only one measure matrix for distance and duration but the routing interface expects one measure matrix per vehicle, we first create slices of measures to be passed in. These slices will then be filled with duplicates of the passed measures. Since the routing interface expects route.ByIndex measures we convert the matrix format into a ByIndex format using the route.Matrix()
function. The complete implementation looks like this:
Now that the data pre-processing is done, we can use the routing model:
You can see that the options for ValueFunctionMeasures
, TravelTimeMeasures
and Shifts
are always used but the options for Starts
and Ends
are only used if data for them is available in the input file.
Returning the solver
Finally, we return a solver
for our router
passing in options that were given at the very beginning by the calling function. This solver is then executed by the run.Run
function from the beginning.
For further understanding of how the router
engine works as part of the route
package, check out the route package how-to guide and the technical reference.
Interim result
Creating a routing app with a custom model in a contained environment is very straight forward, once the needed matrices are given in the correct form factor in the input file. But how can you generate such an input file?
Let's look at the measure-matrix
template now which does exactly this.
Building matrices to be consumed by a routing app
We will start analogously and first check that all files are available in the previously created measure-matrix
folder. Running the tree
command, you should see the file structure.
README.md
gives a short introduction about the matrix generation and shows you how to run the template.go.mod
andgo.sum
define a Go module and are used to manage dependencies, including the Nextmv SDK.input.json
describes the input data for a specific routing problem that is solved by the template.license
contains the Apache License 2.0 under which we distribute this template.main.go
contains the actual code of the Nextmv routing app.- The
measure-matrix.code-workspace
file should be used to open the template in Visual Studio Code. It is pre-configured for you so that you can run and debug the template without any further steps.
Running the app will directly create a file called routing-input.json
which can be directly consumed by the routing app we looked at before.
Dissecting the matrix app
Looking at the main.go
the obligatory package and import definition are available in this app as well:
Two structs are defined, input
and output
. input
represents the data needed to create a measure which we can then transform into a format that can be read by our routing app. This format is represented by the output
struct.
The app starts running in the main()
function and first creates a new input struct which holds a few stops in Berlin to visit, a set of available vehicles
and some optional data as starts
and ends
. These represent fixed start and end locations for each vehicle.
Note that it is ok to provide starts
and ends
or not. It is also ok to partially provide starts
and ends
as shown in the example. The only requirement is that if you want to use a start
or end
location for only a few vehicles you need to make sure to provide empty data for all other vehicles. This is because we are using the indices of each slice to map vehicles with the start/end locations.
When using our clients to retrieve matrices you need to pass a slice of measure.Points
in specific format. You can read more about measures and the different providers we support here. For convenience there is a function that can be used to create this slice easily:
Now that we have the points we can create a client of the provider of our choice and to retrieve the desired matrices. The template uses our OSRM
client but you can simply replace it and use one of our other clients for Google
, Here
or Routingkit
as well.
The last step before writing everything to our output struct is to override costs in the matrix that may not be correct for our needs. If it was decided to not or only partially use start and end locations for vehicles, it can be that, depending on the provider, an infinite cost is applied to reach an "empty" location. But for our routing case what we mean by an "empty" location is that there are no costs attached to reaching this point. For that reason there is a function that does exactly this: set the costs when going to or coming from such "empty" point to zero.
We can then convert the data into a routing app friendly and serializable format. To do that we use three helper functions:
- A function to convert
[]route.Point
to[]route.Stop
:
- A function to convert
[]route.Point
to[]route.Position
:
- A function to convert
route.ByIndex
to[][]float64
:
With those helper function we create our output which serves as an input for our routing app:
An finally, we write the output into a file routing-input.json
.