Reference

Measures (cost)

Technical reference for route measures.

Measures determine the cost of connecting two things together. In routing this means the cost of going from one location to another. This cost can be calculated using a generic measure that simply computes distance or duration, but it can also represent any type of KPI as well, such as monetary value.

Nextmv provides several prebuilt measures and functionality to go with them. The measure interface is standardized, enabling measures to be used interchangeably within the engine. Nextmv can be used with any mapping service, for example:

  • google: Google Distance Matrix API and polylines from Google Maps Distance API.
  • here: HERE Matrix Routing API.
  • osrm: An OSRM server.
  • routingkit: offline Open Street Maps files (no server required).
  • GraphHopper.
  • Other proprietary or internal mapping service.
  • When creating your own measure, it is very important to index stops and vehicles in the expected way. For the measure to work correctly, indices should be given following the format:

    [stop-1, ..., stop-n, vehicle-1-start, vehicle-1-end, ..., vehicle-m-start, vehicle-m-end]

  • If you don't want the solver to consider the cost of going from one location to another, set the corresponding cost to be 0. For example, if vehicles don't have ending locations, the cost of going from a vehicle's end to any other location should be 0.

Types of measures

There are two types of measures:

Each type is represented by a Go interface. Some engines may require a ByPoint measure while others use a ByIndex measure.

To convert a ByPoint to a ByIndex measure use the Indexed method.

Nextmv offers the following pre-built measures, and you can always create your own.

  • Haversine: ByPoint measure that returns the haversine (great-circle) distance between two points.
  • Euclidean: ByPoint measure that returns the euclidean distance between two points.
  • Taxicab: ByPoint measure that returns the taxicab (Manhattan) distance between two points.
  • Matrix: ByIndex measures to look up the cost of going from an origin index to a destination index, requiring a full dataset.
  • Sparse matrix: the same as Matrix, but does not require a full dataset.
  • Routingkit: measures to determine the shortest path between points on road networks; it does not require a client.
  • Google: ByIndex measures to determine the shortest path between points on road networks using Google's Distance Matrix API.
  • Here: ByIndex measures to determine the shortest path between points on road networks using the Here Maps API.
  • OSRM: ByIndex measures to determine the shortest path between points on road networks; it requires an OSRM client.

Point-to-point measures

When cost must be computed based on distance between two points of any dimension, a model can use a ByPoint implementation.

It returns the cost to travel from one point to another.

Points may be of any dimension. If the points passed in to any of these measures have differing dimensionality, they will project the lower dimension point into the higher dimension by appending 0s.

Indexed measures

ByIndex measures refer to an underlying list of points, using indices. It returns the cost to travel from one point at a particular index in the list to another. A ByIndex implementation provides the same functionality as a ByPoint implementation, except its cost method accepts two indices instead of two points.

Indexed measures are more common, and a number of them embed and operate on results from other indexed measures (see Composing and augmenting measures below).

Composing and augmenting measures

In addition to the indexed measures detailed above, a number of ByIndex implementations are also available for augmenting or composing measures.

These additional implementations include:

  • Bin: select from a slice of measures by some function
  • Constant: always returns the same cost
  • Location: adds fixed location costs to another measure
  • Override: overrides some other measure given a condition
  • Power: takes some other measure to a power
  • Scale: scales some other measure by a constant
  • Sum: adds the costs of other measures together
  • Truncate: truncates cost values provided by another measure

Triangularity

To determine the triangularity of a measure, use the IsTriangular function.

When using measures with Nextmv engines, you may need to consider the property of triangular inequality for ByIndex measures in particular.

A measure is considered triangular if, for any three points, the cost of an arc between two of the points will always be less than the cost of an arc between those same two points and a third point. The IsTriangular function reports whether a given ByIndex measure upholds this property.

Some engines, such as vehicle, assume the property of triangular inequality holds for the measures they work with in order to prune their search spaces and find better solutions. However, it also means that if the measure is not triangular, some feasible solutions may not be found by the solver. Whereas the euclidean and haversine measures uphold triangular inequality, the routingkit and OSRM measures may theoretically violate this, although this may be uncommon in practice.

Haversine

To use a haversine measure, call the HaversineByPoint function.

The haversine measure provides more accurate distance estimates for points located on the Earth than a euclidean measure, given its curvature. The haversine formula determines the great-circle distance between two points on a sphere given their longitudes and latitudes. Important in navigation, it is a special case of a more general formula in spherical trigonometry, the law of haversines, that relates the sides and angles of spherical triangles.

Haversine is the default measure used by engines to calculate the distance between two locations.

Euclidean

To use a euclidean measure, call the EuclideanByPoint function.

The euclidean measure finds the euclidean distance between two points, treating the arc between two points as a straight-line segment connecting them as on a grid. The formula for this measures is the square root of the sum of the squared differences of each dimension of the two points.

Taxicab

To use a taxicab measure, call the TaxicabByPoint function.

Taxicab distance treats the path between two points as right angle straight lines (also referred to as "city block" or "Manhattan" distance). Here, the distance between two points is the sum of the absolute differences of their Cartesian coordinates. In Taxicab geometry (as opposed to Euclidean geometry), the hypotenuse is never traversed when computing distance.

Matrix

To use a matrix measure, call the Matrix function.

Matrix is a ByIndex type measure that returns the cost from a row to a column index in a matrix of pre-computed costs (distances, durations, or other costs, e.g., distances with bike-ability level factored in) between two locations. Cost is assumed to be asymmetric.

Please note that when constructing a Matrix measure, you should account for all stops and vehicles' start and end locations in the correct order. Assume you have two stops (s1 and s2) and two vehicles (v1 and v2). Here is a sample Matrix measure:

package main

import (
	"fmt"

	"github.com/nextmv-io/sdk/route"
)

func main() {
	arcs := [][]float64{
		{
			/*s1->s1*/ 0.,
			/*s1->s2*/ 1.,
			/*s1->v1-start*/ 2.,
			/*s1->v1-end*/ 3.,
			/*s1->v2-start*/ 4.,
			/*s1->v2-end*/ 5.,
		},
		{
			/*s2->s1*/ 1.,
			/*s2->s2*/ 0.,
			/*s2->v1-start*/ 2.,
			/*s2->v1-end*/ 3.,
			/*s2->v2-start*/ 4.,
			/*s2->v2-end*/ 5.,
		},
		{
			/*v1-start->s1*/ 1.,
			/*v1-start->s2*/ 2.,
			/*v1-start->v1-start*/ 0.,
			/*v1-start->v1-end*/ 3.,
			/*v1-start->v2-start*/ 4.,
			/*v1-start->v2-end*/ 5.,
		},
		{
			/*v1-end->s1*/ 1.,
			/*v1-end->s2*/ 2.,
			/*v1-end->v1-start*/ 3.,
			/*v1-end->v1-end*/ 0.,
			/*v1-end->v2-start*/ 4.,
			/*v1-end->v2-end*/ 5.,
		},
		{
			/*v2-start->s1*/ 1.,
			/*v2-start->s2*/ 2.,
			/*v2-start->v1-start*/ 3.,
			/*v2-start->v1-end*/ 4.,
			/*v2-start->v2-start*/ 0.,
			/*v2-start->v2-end*/ 5.,
		},
		{
			/*v2-end->s1*/ 1.,
			/*v2-end->s2*/ 2.,
			/*v2-end->v1-start*/ 3.,
			/*v2-end->v1-end*/ 4.,
			/*v2-end->v2-start*/ 4.,
			/*v2-end->v2-end*/ 0.,
		},
	}
	m := route.Matrix(arcs)

	fmt.Printf("0(s1) -> 3(v1-end): %f\n", m.Cost(0, 3))
	fmt.Printf("4(v2-start) -> 2(v1-start): %f\n", m.Cost(4, 2))
}
Copy

Sparse matrix

To use a sparse matrix measure, call the Sparse function.

In addition to the Matrix measure, Nextmv also provides a Sparse matrix measure. The Sparse matrix is a ByIndex measure that returns the cost from a row to a column index in a matrix of pre-computed costs (distances, durations, or other costs, e.g., distances with bikeability level factored in) between two locations without requiring a full distance matrix. If two locations do not have an associated cost, then a backup measure is used. Use a sparse matrix measure whenever you have a lot of points that are sparsely connected. This helps to significantly reduce memory requirements of your application.

The Sparse matrix measure requires a ByIndex measure and arcs as input, and can be constructed as:

package main

import (
	"fmt"

	"github.com/nextmv-io/sdk/route"
)

func main() {
	arcs := [][]float64{
		{10., 20.},
		{20., 30.},
	}
	fallback := route.Matrix(arcs)
	sparse := map[int]map[int]float64{
		0: {1: 30.},
		1: {0: 20.},
	}
	m := route.Sparse(fallback, sparse)

	fmt.Printf("0 -> 1: %f\n", m.Cost(0, 1))
	fmt.Printf("1 -> 1: %f\n", m.Cost(1, 1))
}
Copy

Routingkit

To use a routingkit measure, call one of the routingkit functions.

While haversine and euclidean measures can quickly estimate the cost of traveling between two points, these measures are limited in their accuracy because they don't take real-world road networks into account. The measures in the routingkit package use mapping data from the OpenStreetMap (OSM) project to determine the cost of navigating from one point to another along the shortest possible path. Under the hood, the routingkit measure uses a Go wrapper we created for RoutingKit, an open source C++ library.

We provide two sets of measures:

  • ByPoint: implementations that calculate the cost at query time.
  • Matrix: measures that pre-compute all costs between pairs of points up-front.

Please see the routingkit functions for more details and examples on how routingkit estimations can be obtained.

Here are some details on the arguments that are passed to the routingkit functions to create a measure.

  1. To instantiate a routingkit measure, you'll need to have an OSM file that covers the region containing all the points your model may route to. Geofabrik hosts OSM files for broad geographic areas such as countries and U.S. states. If you are targeting a more specific area, the OSM export tool allows you to create OSM files covering a custom bounding box. Using a larger OSM file will increase the memory required by the routingkit measure (and thus, your model).

    In each routingkit function, the first argument is the path to an .osm.pbf file. The first time a routingkit measure is initialized with a particular osm filepath, a new file will be created in the same directory as the provided osm file. This file contains a data structure called a contraction hierarchy (CH), which contains indices that speed up queries. It will have the same base name as the osm.pbf file, but will have an extension following the format .osm.pbf_<car|pedestrian|bike>_<duration|distance>.ch. Building the contraction hierarchy will cause the routingkit measure to take longer to initialize if the .ch file is not already present, but this step will be skipped in the future if the .ch file persists between runs.

  2. The next argument for a routingkit function is the maximum snap distance. Because not all geocoordinates fall exactly on a road, such points will be "snapped" to the nearest location on the road network. But if the distance between the nearest road network point and the queried point is greater than the maximum snap distance, the routingkit measure's Cost function will return a sentinel value from the go-routingkit package, a maximum distance. This value is also returned in the event no route can be found between two points (for example, if the two points are connected by a restricted road).

  3. ByPoint measures cache costs between points in order to improve performance. The maximum memory used by this cache is configured by the third argument to these functions.

  4. After that the travel profile must be specified. We included our own profiles that help you set up your measure. All profiles are exported by the go-routingkit package. Using those profiles, routingkit will only use roads and ways that can actually be traversed by the form of transportation. The same way the standard profiles are created, you can create your own profiles and pass them into the constructor to match your specific routing requirements.

    The goroutingkit.Profile needs 6 parameters to build a profile:

    • Name: The name of the profile

    • TransportationMode: The transportation mode pre-selects valid ways for the type of transportation. The valid options are: goroutingkit.VehicleMode, goroutingkit.BikeMode and goroutingkit.PedestrianMode.

    • Prevent left turns flag: This flag is used to allow or forbid left turns. It will only be used if the transportation mode is set to goroutingkit.VehicleMode.

    • Prevent U-turns flag: This flag is used to allow or forbid U-turns. It will only be used if the transportation mode is set to goroutingkit.VehicleMode.

    • TagMapFilter func: This function is used to create a map of allowed OSM way tags. Ways that do not pass the filter will not be used.

    • SpeedMapper func: This function is used to assign a speed value to every allowed way tag, defined in the TagMapFilter function.

  5. The final argument to each function - a fallback measure - allows you to override this default behavior when no road network path can be found between two points. If for any reason the max distance is returned as the cost for any two points, this fallback measure will be invoked with the points, and this fallback result will be returned from Cost instead. For example, you may choose to fall back to a scaled Haversine measure or Manhattan measure to estimate distances in the event their exact road network route cannot be found.

    If you pass nil for the fallback measure, it is up to you to handle costs of the maximum distance, for example by throwing an error when this value is encountered.

How to deploy a routingkit-measure-based model to AWS Lambda

Deploying a model that uses the routingkit measure to AWS Lambda offers two challenges: OSM and CH files must be present on the file system when your model starts; and your model will require dynamic runtime linking to a C standard library.

Pre-build CH files (optional but recommended).

We recommend creating the CH file in advance and making it available via the file system using the same mechanism you use to provide the OSM file (discussed more below), so it does not need to be rebuilt on every model invocation, taking up unnecessary CPU time. You can prebuild a CH file for a particular OSM file simply by instantiating a routingkit measure.

Download OSM and CH files before the solver runs.

One simple solution is to store your CH files in S3, then copy them to /tmp if they are not already present on each Lambda invocation. The /tmp directory may persist between Lambda runs, so some runs may not need do the extra work of pulling down the OSM files. That said, pulling even large files from S3 into AWS Lambda should be very fast. Here is a code example:

package main

import (
	"os"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"
)

func main() {
	osmFile := `/tmp/region.osm.pbf`
	chFile := `/tmp/region.ch`
	if _, err := os.Stat(osmFile); os.IsNotExist(err) {
		if err := download("bucket", "region.osm.pbf", osmFile); err != nil {
			panic(err)
		}
	}
	if _, err := os.Stat(chFile); os.IsNotExist(err) {
		if err := download("bucket", "region.ch", chFile); err != nil {
			panic(err)
		}
	}
}

func download(bucket, key, file string) error {
	s, err := session.NewSession()
	if err != nil {
		return err
	}
	f, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE, 0o655)
	if err != nil {
		return err
	}
	downloader := s3manager.NewDownloader(s)
	s3input := &s3.GetObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
	}

	_, err = downloader.Download(f, s3input)

	return err
}
Copy

Note that AWS Lambda's temporary directory has a size of 512MB. If your OSM and CH files jointly exceed this size, you will need to take another approach, like mounting an Amazon Elastic File System (EFS) into your Lambda.

Change your Lambda settings to use amazonlinux2.

Your binary will be built using CGO and the underlying RoutingKit C++ library must be able to dynamically link against a C standard library version that is compatible with the version the binary was built with. If glibc is the standard library implementation you use, version 2.26 or higher is required.

While the default AWS Lambda image has an older version of glibc, the amazonlinux 2 image includes glibc version 2.26. You can configure your Lambda to use this image by using the dropdown under Runtime to select "Provide your own bootstrap on Amazon Linux 2" when creating your lambda function. If creating your Lambda function with SAM, enter provided.al2 under the Runtime setting.

Note that your model binary also has to be linked against glibc version 2.26 at build time. An easy way to ensure this happens is to build your model in an amazonlinux:2 container. Alternatively, you could statically link your binary using musl (which would require musl to be linked as the C standard libary implementation at build time).

Google

The google package provides ByIndex type measures for estimating distances and durations using the Google Distance Matrix API. This measure is built on top of the matrix measure and it uses the Go client for Google Maps Services. A Google Maps client and request are required.

The client uses an API key for authentication. At a minimum, the request requires the origins and destinations to provide an estimate of distance and duration. You can also use the Polylines function to retrieve polylines for a set of points from Google. The function returns a polyline from first to last point as well as a slice of polylines, one for each leg of the given points. All polylines are encoded in Google's polyline format.

We recommend you visit the examples to understand how to build a Google measure.

HERE

The here package provides ByIndex type measures for estimating distances and durations using the HERE Maps API. A HERE Maps Client is required to make requests to the HERE Maps API. The Client uses an API key for authentication.

Functions also take a variadic list of MatrixOptions that configure how HERE will calculate the routes. Here are the available options (see the HERE API reference for more details about these options):

  • WithDepartureTime(t time.Time): this option sets the departure time that will be used for the calculated routes. If this option is not set (or is set to the zero-value), HERE will not consider traffic.
  • WithTransportMode(mode TransportMode): this configures the measure to use the appropriate transport mode - for example, the pedestrian transport mode allows pedestrian-exclusive paths to be taken when determining route lengths.
  • WithAvoidFeatures(features []Feature): this option sets features such as toll roads that should be avoided in the routes HERE creates.
  • WithAvoidAreas(areas []BoundingBox): takes a list of areas (defined by two latitude and two longitude values, forming a bounding box) that should be avoided by all calculated routes.
  • Three MatrixOptions allow you to configure routes when using specific transport modes, and must be used with a compatible TransportMode option: WithTruckProfile(t Truck), WithScooterProfile(s Scooter), and WithTaxiProfile(t Taxi).

We recommend you visit the examples to understand how to build a HERE measure.

OSRM

To use an osrm measure, call one of the osrm functions.

The osrm package provides an Open Source Routing Machine (OSRM) REST service that can be queried to find shortest paths in road networks. We include a ByIndex type measure located in the package that requests a matrix of point distances from an OSRM server. This measure is built on top of the matrix measure.

See the OSRM repo for information about setting up your server. You will need to be able to reach the service from the system your model will be deployed to.

Here are some considerations when instantiating and OSRM measure

  • Call the osrm.DefaultClient constructor, passing it the hostname of your OSRM server.
  • The second argument indicates whether a cache should be used. The resulting client has a SnapRadius method that can be used to configure a snapping radius for all points that aren't directly on the road network of or even outside the hosted area.
  • If you are using OSRM to receive durations via the DurationMatrix function, there is also a ScaleFactor method on the client which will multiply every duration returned from OSRM by the configured factor.
  • The MaxTableSize method lets you set this parameter which will influence how requests will be sliced. If you set this value to the same value as with your OSRM server, the server will always be able to handle the requests coming from the client.
  • You can also use the Polyline function to retrieve polylines for a set of points from the OSRM server. The function returns a polyline from first to last point as well as a slice of polylines, one for each leg of the given points. All polylines are encoded in Google's polyline format.
  • You will need to decide upfront whether to calculate costs using either distances (in meters), durations (in seconds), or both.
  • The parallelQueries argument specifies the number of parallel queries to be made. You can use 0 to calculate a default, based on the number of points given.

We recommend you visit the examples to understand how to build OSRM measures.

Page last updated

Go to on-page nav menu