Road Distance Matrix API for Logistics: Distances Between Multiple Points

A customer appears closer to depot A than depot B on the map, but the river crossing makes A the longer journey. Before assigning the order, compare the road journeys from both depots. A road distance matrix API does exactly that comparison: it returns a table of distances or durations for selected origin–destination pairs. It does not allocate stock, reserve a driver or decide which customer should be served first.

TrackAsia Matrix v1 takes a profile, a coordinate list and optional source and destination indices.[2] The important implementation work is preserving the link between those indices and your business records. A numerically valid table attached to the wrong depot IDs can produce a convincing but wrong decision.

A rectangular table is usually what you need

Suppose three depots can serve eight customers. Comparing each depot with each customer requires 3 × 8 = 24 pairs. It does not require an eleven-by-eleven table containing depot-to-depot and customer-to-customer journeys. This is a calculation of required output, not a statement about the account’s billing unit.

Rows represent the selected sources in their requested order; columns represent selected destinations. Keep explicit arrays of depot and customer IDs alongside the coordinates. Never sort one array after constructing the request unless you also rebuild every corresponding index. When results are stored, save the IDs and profile with them, not just a table of numbers whose meaning depends on yesterday’s array order.

A full square matrix becomes useful when later calculations need travel between every stop, such as an optimiser evaluating different sequences. That is a different workload from choosing a depot. Decide which decision the table supports before selecting all coordinates by default.

Read distance and duration correctly

The Matrix v1 documentation states that distances describe the fastest routes, not necessarily the shortest-distance routes. Distances are metres and durations are seconds.[2] A route of 8 kilometres taking 12 minutes can therefore be the selected route even if a slower 6-kilometre route exists. Do not label the returned distance “the minimum possible kilometres” in a quote or dispatch screen.

A-to-B also need not equal B-to-A. One-way roads, access roads and the side of the road can change a journey. Keep direction in the cache key and test both directions when the business needs both. Copying one triangular half of a table into the other is an application assumption, not an API guarantee.

Duration is an input to planning. Loading time, waiting for security, breaks and the customer’s receiving window are additional business quantities. A matrix result alone is not a promised arrival time or evidence of live traffic prediction. If the product needs time-dependent estimates, obtain explicit documentation and validate that separate requirement.

Choose the vehicle profile deliberately

The documented profiles include car, moto, truck and walk.[2] Use the profile relevant to the journey being evaluated and keep it with every saved result. Do not compare a motorcycle run with a car run and attribute the difference to provider quality.

The presence of a truck profile does not establish support for a particular vehicle height, gross weight, hazardous cargo rule or time-based urban restriction. Those are separate requirements that need documented parameters and operational validation. A dispatcher must still verify restrictions relevant to the actual vehicle and trip.

Coordinate order is another easy mistake. Matrix places longitude,latitude pairs in the URL path. TrackAsia Directions v2, by contrast, documents latitude,longitude for its origin and destination parameters.[2][10] Use named {lng, lat} objects internally and convert at the boundary instead of passing unlabeled pairs through the whole application.

Run a complete depot-to-customer example

Save this as matrix.mjs and run it with Node.js 20 or later: node matrix.mjs. It uses the documented limited public key for a small trial; set TRACKASIA_KEY locally for your own configured account key. The depot and customer names are illustrative, and the points are demonstration coordinates, not verified business premises.

const depots = [
  { id: 'depot-A', lng: 106.698, lat: 10.773 },
  { id: 'depot-B', lng: 106.720, lat: 10.795 }
];
const customers = [
  { id: 'customer-1', lng: 106.705, lat: 10.780 },
  { id: 'customer-2', lng: 106.710, lat: 10.785 }
];
const points = [...depots, ...customers];
for (const point of points) {
  if (!Number.isFinite(point.lng) || !Number.isFinite(point.lat)
      || Math.abs(point.lng) > 180 || Math.abs(point.lat) > 90) {
    throw new Error(`Invalid coordinate: ${point.id}`);
  }
}
const coordinates = points.map(p => `${p.lng},${p.lat}`).join(';');
const url = new URL(`https://maps.track-asia.com/distance-matrix/v1/car/${coordinates}`);
url.search = new URLSearchParams({
  sources: depots.map((_, i) => i).join(';'),
  destinations: customers.map((_, i) => depots.length + i).join(';'),
  annotations: 'distance,duration',
  key: process.env.TRACKASIA_KEY || 'public_key'
});
const response = await fetch(url, { signal: AbortSignal.timeout(15000) });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
if (data.code !== 'Ok') throw new Error(`Matrix: ${data.code}`);
for (const table of [data.distances, data.durations]) {
  if (!Array.isArray(table) || table.length !== depots.length
      || table.some(row => !Array.isArray(row) || row.length !== customers.length)) {
    throw new Error('Unexpected matrix dimensions');
  }
}
const pairs = depots.flatMap((depot, i) => customers.map((customer, j) => {
  const metres = data.distances[i][j];
  const seconds = data.durations[i][j];
  const available = Number.isFinite(metres) && metres >= 0
    && Number.isFinite(seconds) && seconds >= 0;
  return { depotId: depot.id, customerId: customer.id,
    metres: available ? metres : null,
    seconds: available ? seconds : null,
    state: available ? 'routed' : 'unavailable' };
}));
console.table(pairs);

The response should have two distance rows and two duration rows because the request selects two depots and two customers. Four output objects connect the numbers back to business IDs. The code keeps metres and seconds unchanged; convert to kilometres or minutes only for display. It deliberately does not print an invented expected distance, because the value depends on the service’s routing data and can change.

The sample also avoids fallback_speed. That keeps the meaning simple: the decision process should reject an unavailable road journey rather than silently substitute an estimate. If the key is refused or the service returns an error, the program stops instead of manufacturing an empty success table.

Missing cells must never become free journeys

Check HTTP status, code, table dimensions and individual cells. Number(null) is zero in JavaScript, so converting everything with Number() can accidentally turn an unavailable route into the most attractive journey. The example tests finite numbers before doing any arithmetic.

A genuine zero can be valid for identical or effectively coincident points. That is different from a missing value. Preserve the distinction and inspect unusually small results when depots and customers are expected to be far apart. Invalid geocoding upstream can make a perfect matrix calculation operationally useless.

The documentation offers fallback_speed: when a route cannot be found, a crow-flight distance can be used with that speed to estimate duration.[2] If your application uses it, clearly separate estimated pairs from routed pairs and define whether those pairs can participate in assignment. Do not promise road access based on a straight-line fallback. If the response does not let your application identify estimates reliably enough for the use case, do not enable the fallback for that decision.

Move from one request to a larger workload

Confirm request-size and account limits before choosing a batch size. A useful batching design takes a subset of origins and a subset of destinations, constructs the combined coordinate list, and writes each returned cell with its global business IDs. Local coordinate indices start again for each request; global depot IDs do not.

Persist completed batches immediately and record profile, coordinate version and processing time. If a customer’s confirmed point changes, old values should not remain eligible merely because the customer ID stayed the same. Coordinate versioning is more reliable than a cache indexed only by customer name.

Retry transient network failures with bounded delay. A malformed coordinate needs correction; an authentication failure needs configuration; an unavailable journey needs a business exception. Avoid retrying all three as if they were temporary congestion. Request logs should expose endpoint, duration and outcome without exposing the complete URL containing an API key.

For repeated comparisons, ask what storage and reuse the agreement allows. Technical ability to cache a table is not automatically permission to retain it indefinitely. Your own branch coordinates and provider-derived route results can have different retention rules.

Try the source: Compare depots with a road matrix

Request a 2-depot × 2-customer matrix and inspect all four pairs in metres and seconds before assignment.

API examples use the limited public_key; the image captures a run of the source below.

Unzip, run python3 -m http.server 8080 in the extracted folder, then open http://localhost:8080/index-en.html. Vietnamese and English READMEs are included; no npm or build step is needed.

Download complete source ZIP

Compare depots with a road matrix
Tap the image to open full size.

Turn the table into a decision without overclaiming

For one customer, selecting the available depot with the lowest duration is a small ranking rule. Add stock and service-area eligibility before ranking, otherwise the shortest journey may select a depot that cannot fulfil the order. Display why a depot was excluded so an operator can distinguish unavailable stock from unavailable routing.

For many orders, independently choosing the nearest driver can allocate the same driver to several simultaneous pickups. A matching algorithm or a vehicle-routing solver handles a different decision involving shared resources. Matrix supplies journey costs; an optimiser and your business rules supply the assignment. Keep those responsibilities visible when explaining the feature to operations.

Service time also belongs outside the travel matrix. If unloading takes an illustrative 15 minutes at each customer, add it as an explicit planning assumption. Do not modify the raw matrix values to hide that allowance: keeping travel and service time separate makes later discrepancies easier to investigate.

Test the pairs that matter to your operation

Use a small reference set containing a river crossing, a one-way journey in both directions, a hard-to-access site and two coincident points. Verify coordinate order and ID mapping before evaluating route suitability. Run the same profile and selected pairs when comparing another provider; otherwise the test is not answering the same question.

Review the route visually with Directions for selected suspicious pairs, rather than requesting detailed geometry for every matrix cell. The matrix article is about comparison; route display is a separate integration and workload. Record unexpected cases with the actual endpoints, profile and coordinates so support can reproduce them.

Before rollout, agree how dispatch handles no-route pairs and how often comparisons need refreshing. Then give TrackAsia the expected origins, destinations, recalculation frequency and peak concurrency to confirm limits and billing units. A useful matrix integration makes the decision explainable and the exceptions safe, not just the table fast to obtain.

Store pairs in a form that can be audited

A practical result record contains origin ID, destination ID, both coordinate versions, profile, metres, seconds, processing time and outcome. These are proposed application fields. They make it possible to ask why an assignment used a particular value without reconstructing an old request from memory. Keep the original numeric values separate from rounded labels such as “about ten minutes”.

When joining results to orders, reject unmatched IDs instead of relying on row position in a spreadsheet export. A removed customer or a newly inserted depot can shift positions while leaving the table rectangular. An explicit join exposes the missing reference; a positional merge can silently attach every later value to the wrong record.

For an operator-facing comparison, display the selected profile and time the table was generated. If the coordinate changed afterwards, mark the result stale. This is especially important when a depot moves its loading gate without changing its commercial address. The saved point, rather than the address label alone, determines the journey being compared.

Use a small test fixture before increasing volume

Create a local fixture with two sources and two destinations whose IDs are visibly different. Assert that every requested pair appears exactly once, that reversing the source list reverses output rows as expected, and that a null cell remains unavailable. These checks test your mapping code without assuming a particular live route distance.

Then run a live sample to check the documented response shape and practical route suitability. Keep those two tests separate: a stable local fixture protects your parser, while a live response checks the integration at that time. Neither one proves that every future customer point is reachable or that the application’s assignment policy is commercially correct.

Sources

[2] https://docs.track-asia.com/api-integration/distance-matrix/v1
[10] https://docs.track-asia.com/api-integration/directions/v2