N-Ways
Developer docsDispatch matrix

Sample: dispatch matrix

Assign each new order to the rider who can reach it first, using real travel times instead of straight lines.

On this page

New orders come in; riders are spread across the city. Straight-line distance is a poor guide in Lagos or Accra: the nearest rider may be across a lagoon, or on the wrong side of an expressway with no turn for three kilometres. This sample asks the route matrix for the real travel time from every free rider to every waiting order in one request, then gives each order to the rider who gets there first.

How it works

  1. Collect the free riders’ last positions (from your rider app, or fleet tracking) and the waiting orders’ pickup points.
  2. Send riders as origins and pickups as destinations to POST /maps/v1/routes/matrix: up to 50 of each and 625 pairs a request.
  3. Go through the pairs from the shortest time up, and assign a pair when both the rider and the order are still free (a greedy assignment: simple, fast, and close to the best for dispatch).
  4. Skip pairs the matrix says can’t be done (ROUTE_NOT_FOUND), and pairs longer than your promise to the customer.
Note:

The sample plans your couriers’ bikes as dispatch: they keep off roads closed to every motorcycle, but commercial okada bans don’t apply. If your riders carry passengers (okadas), use okada: pairs inside a ban area then come back NOT_ALLOWED.

The code

Run it on your server with a key restricted to the server’s IP address and to the Route matrix API. Node.js 18 or later, or Python 3.8 or later, with nothing to install.

dispatch.mjs
// Assign each waiting order to the free rider who can reach its pickup first.
// node dispatch.mjs   (NWAYS_API_KEY in the environment)
const KEY = process.env.NWAYS_API_KEY
const MAX_MINUTES = 25 // don't send a rider further than this

const riders = [
  { id: 'rider-ade', at: { lat: 6.6018, lng: 3.3515 } }, // Ikeja, Allen Avenue
  { id: 'rider-bisi', at: { lat: 6.5005, lng: 3.3581 } }, // Surulere
  { id: 'rider-chidi', at: { lat: 6.4474, lng: 3.4733 } }, // Lekki Phase 1
]
const orders = [
  { id: 'order-1041', pickup: { lat: 6.5965, lng: 3.3421 } }, // Ikeja GRA
  { id: 'order-1042', pickup: { lat: 6.4413, lng: 3.4250 } }, // Victoria Island
  { id: 'order-1043', pickup: { lat: 6.5095, lng: 3.3711 } }, // Yaba
]

async function matrix(origins, destinations) {
  const res = await fetch('https://nways.ng/maps/v1/routes/matrix', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'X-NWays-Api-Key': KEY },
    body: JSON.stringify({ origins, destinations, mode: 'dispatch' }),
  })
  const data = await res.json()
  if (!res.ok) throw new Error(`${res.status} ${data.error?.reason ?? ''}: ${data.error?.message ?? ''}`)
  return data.elements
}

const elements = await matrix(riders.map((r) => r.at), orders.map((o) => o.pickup))
const pairs = elements
  .filter((e) => e.status === 'OK' && e.durationS <= MAX_MINUTES * 60)
  .sort((a, b) => a.durationS - b.durationS)

const busy = new Set()
const done = new Set()
for (const e of pairs) {
  const rider = riders[e.originIndex]
  const order = orders[e.destinationIndex]
  if (busy.has(rider.id) || done.has(order.id)) continue
  busy.add(rider.id)
  done.add(order.id)
  console.log(`${order.id} → ${rider.id}: ${Math.round(e.durationS / 60)} min, ${(e.distanceM / 1000).toFixed(1)} km`)
}
for (const o of orders) if (!done.has(o.id)) console.log(`${o.id}: no free rider within ${MAX_MINUTES} min`)
dispatch.py
"""Assign each waiting order to the free rider who can reach its pickup first.
python3 dispatch.py   (NWAYS_API_KEY in the environment)"""
import json
import os
import urllib.error
import urllib.request

KEY = os.environ["NWAYS_API_KEY"]
MAX_MINUTES = 25  # don't send a rider further than this

riders = [
    {"id": "rider-ade", "at": {"lat": 6.6018, "lng": 3.3515}},  # Ikeja, Allen Avenue
    {"id": "rider-bisi", "at": {"lat": 6.5005, "lng": 3.3581}},  # Surulere
    {"id": "rider-chidi", "at": {"lat": 6.4474, "lng": 3.4733}},  # Lekki Phase 1
]
orders = [
    {"id": "order-1041", "pickup": {"lat": 6.5965, "lng": 3.3421}},  # Ikeja GRA
    {"id": "order-1042", "pickup": {"lat": 6.4413, "lng": 3.4250}},  # Victoria Island
    {"id": "order-1043", "pickup": {"lat": 6.5095, "lng": 3.3711}},  # Yaba
]


def matrix(origins, destinations):
    body = json.dumps({"origins": origins, "destinations": destinations, "mode": "dispatch"}).encode()
    req = urllib.request.Request(
        "https://nways.ng/maps/v1/routes/matrix",
        data=body,
        headers={"Content-Type": "application/json", "X-NWays-Api-Key": KEY},
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=20) as res:
            return json.load(res)["elements"]
    except urllib.error.HTTPError as e:
        err = json.load(e).get("error", {})
        raise SystemExit(f"{e.code} {err.get('reason')}: {err.get('message')}")


elements = matrix([r["at"] for r in riders], [o["pickup"] for o in orders])
pairs = sorted(
    (e for e in elements if e["status"] == "OK" and e["durationS"] <= MAX_MINUTES * 60),
    key=lambda e: e["durationS"],
)

busy, done = set(), set()
for e in pairs:
    rider, order = riders[e["originIndex"]], orders[e["destinationIndex"]]
    if rider["id"] in busy or order["id"] in done:
        continue
    busy.add(rider["id"])
    done.add(order["id"])
    print(f"{order['id']} -> {rider['id']}: {round(e['durationS'] / 60)} min, {e['distanceM'] / 1000:.1f} km")
for o in orders:
    if o["id"] not in done:
        print(f"{o['id']}: no free rider within {MAX_MINUTES} min")

What it prints

Something like this:

Text
order-1041 → rider-ade: 6 min, 2.1 km
order-1043 → rider-bisi: 9 min, 3.0 km
order-1042 → rider-chidi: 14 min, 6.2 km

Make it yours

  • More than 50 riders or orders? Split the riders into groups of 25 and the orders into groups of 25, and ask a matrix for each pair of groups (625 pairs each).
  • Batching. To give one rider several orders in the best order, use route optimisation instead.
  • Fairness. Add a small penalty to riders who just finished a trip far away, or who have already had more trips today than the others, before sorting.
  • Cost. The matrix is billed per pair (element): 3 riders × 3 orders is 9 elements. Pricing