OR-Tools vs Google Maps API for technician routing

When a scheduling system inside Technician Assignment Algorithms decides which crew visits which towers, a second engineering question follows immediately: which off-the-shelf tool should turn that assignment into an actual route? Two names dominate the conversation — Google OR-Tools and the Google Maps Platform routing APIs — and they get treated as interchangeable more often than they should be, mostly because both carry the Google name and both output a sequence of stops. They solve different problems. OR-Tools is a combinatorial optimizer that reasons about time windows, technician skills, and vehicle capacity; the Maps Platform is a geography service that reports how long a real road segment takes to drive, with live traffic folded in. Neither one replaces the other, and picking the wrong one for a given job produces routes that are either geographically naive or contractually illegal. This page lays out what each tool actually does, compares them head-to-head across the constraints that matter for tower dispatch, and ends with a hybrid architecture — Maps supplies the travel-time matrix, OR-Tools solves the constrained assignment on top of it — plus a runnable example that seals its output with a SHA-256 audit hash.

Prerequisites & Context

You need Python 3.9 or newer. OR-Tools installs with pip install ortools and ships prebuilt wheels for Linux, macOS, and Windows; no compiler is required. The Google Maps Platform side needs a billing-enabled Google Cloud project and an API key scoped to the Distance Matrix and Directions APIs — treat that as an external network dependency, not a local library, because every element in a request is a metered call against your account.

This page assumes the harder question of who is dispatched has already been answered. If technician certifications or a crew’s daily capacity still need to be encoded before routing makes sense, that belongs to Crew Capacity & Shift Planning, which models the skill and workload constraints a router later has to respect. If the goal is simply ordering an already-assigned set of stops with a fast, explainable sweep — no solver comparison needed — the direct implementation is Optimizing technician routes for multi-site maintenance windows, which this page treats as the baseline that a solver-based approach either extends or replaces once site counts climb. Both pages sit inside the same Intelligent Inspection Scheduling & Technician Routing pipeline: turning a validated, constraint-cleared work list into a time-stamped itinerary a technician can execute.

Two different jobs

Google OR-Tools is a constraint-programming and combinatorial-optimization library. Its routing module solves variants of the Vehicle Routing Problem — capacitated VRP, VRP with Time Windows (VRPTW), multi-depot VRP — by searching a solution space, not by looking anything up. It takes a distance or time matrix as input: OR-Tools has no idea where TWR-8842 physically is, and it does not know a road exists between two towers unless the matrix says so. What it does know how to do is enforce hard constraints across the whole fleet simultaneously — a technician cannot be at two sites at once, a stop must fall inside its lease-mandated access window, a crew cannot exceed its vehicle’s capacity or attempt a climb requiring a certification it doesn’t hold — and it does this with an exact or near-exact search rather than a single deterministic pass. It runs entirely in-process, needs no network connection once the matrix is loaded, and carries no licensing cost: it is released under the Apache 2.0 license.

The Google Maps Platform solves the opposite half of the problem: it knows geography. The Distance Matrix API returns real drive-time and distance for up to 25 origins by 25 destinations (100 elements) per request, incorporating live and predicted traffic; the Directions API turns a chosen sequence into turn-by-turn navigation; the Roads API snaps noisy GPS traces back onto the actual road network. None of these compute an optimal visiting order for a fleet under time-window, skill, or capacity constraints — the Routes API’s limited waypoint reordering optimizes for shortest total drive time on a single vehicle with no notion of lease windows, curfews, or crew certifications. Every call is billed, requires a live network round-trip, and depends on Google’s infrastructure being reachable — an assumption that fails routinely at towers in fringe coverage areas or below-grade equipment shelters.

Put simply: OR-Tools decides in what order and by whom, subject to hard constraints; the Maps Platform tells you how long it actually takes to get there. A production router almost always needs both, wired together rather than chosen between.

Head-to-head comparison

OR-Tools vs Google Maps Platform: six-row comparison matrix A table with two columns, OR-Tools and Google Maps Platform, and six rows: time-window constraints, skill and capacity constraints, live traffic, offline use, cost model, and licensing. OR-Tools cells are teal-tinted for native, hard-constraint support, free cost, offline operation, and open-source licensing, with an amber-tinted cell noting it has no live traffic data. Google Maps Platform cells are amber-tinted for the constraints it does not model, requiring a network call, per-request billing, and proprietary terms, with a violet-tinted cell for its real-time and predictive traffic strength. Constraint / capability OR-Tools (VRPTW solver) Google Maps Platform Time-window constraints Native hard constraint (VRPTW time dimension) No solver — returns only a travel-time matrix Skill & capacity constraints Native — capacity dimensions and skill vectors Not modeled Live traffic Static input only — no live conditions Real-time & predictive traffic built in Offline use Fully offline, on-prem solve Requires a network call per request Cost model Free — self-hosted compute only Per-request billing (Distance Matrix API) Licensing Apache 2.0, open source Proprietary, Maps Platform ToS

Figure: OR-Tools versus Google Maps Platform across the six constraints that decide tower routing architecture.

The pattern in the matrix is deliberate, not decorative: OR-Tools is teal (native, hard-constraint) for every row that concerns legality — time windows, skill and capacity limits — plus offline operation, cost, and license, and amber only where it genuinely has nothing to offer, live traffic. The Maps Platform is the mirror image: amber everywhere OR-Tools is teal, because it was never built to reason about constraints, and violet — its one clear strength — only for live traffic. Reading the table as a whole answers the “which one do I need” question directly: if the failure mode you are worried about is a technician arriving at a site whose lease access window already closed, that is an OR-Tools problem. If the failure mode is a technician being 40 minutes late because the router assumed free-flowing traffic through a city center at rush hour, that is a Maps Platform problem. Most real deployments have both failure modes simultaneously, which is why neither tool is optional once a route touches more than a handful of sites.

The production pattern is to let each tool do only the job it is good at: call the Distance Matrix API once per shift (or per batch of shifts) to build a time-dependent travel-time matrix for the day’s confirmed sites, then hand that matrix to OR-Tools as the transit callback for a VRPTW model that also encodes each site’s lease window, curfew, and any technician skill requirement. OR-Tools searches the constrained assignment space and returns an ordered, feasible route; only after a route is chosen does it make sense to call the Directions API once more, on the winning sequence only, to generate the turn-by-turn instructions a technician’s phone actually displays. This ordering matters for cost control: a naive integration calls Maps once per candidate ordering explored during search, which can multiply API spend by orders of magnitude, while calling it once for the matrix and once for the final route keeps billing flat regardless of how large the search space is.

Two operational details make this pattern resilient rather than fragile. First, snapshot and hash the fetched matrix before it enters the solver — live traffic means the same query issued five minutes later can return different numbers, and an auditor asking why a route looked the way it did needs the exact inputs OR-Tools actually saw, not a live re-query that no longer matches. Second, keep a pure-Python fallback matrix (Haversine distance times an average speed, or the last successfully cached matrix) so a Maps Platform outage or a below-grade tower shelter with no signal degrades the router to a slightly less accurate but still-running system rather than an outright failure. The runnable example below implements exactly that fallback, guarding the OR-Tools import itself so the same script degrades gracefully in an environment where the package isn’t installed.

Complete Runnable Example

This script models a small VRPTW: a depot plus three towers, each with a lease-mandated access window, matched against a hand-built travel-time matrix of the kind the Distance Matrix API would return. It calls OR-Tools when available and falls back to a pure-Python nearest-feasible-neighbor sweep when it is not, and either path ends in the same audited output: a stop order sealed with a SHA-256 hash. A custom RoutingSolverError fires whenever neither engine can produce a feasible sequence, rather than silently returning a partial or illegal route.

python
import hashlib
import json
import logging
from datetime import datetime, timezone

try:
    from ortools.constraint_solver import pywrapcp, routing_enums_pb2
    HAS_ORTOOLS = True
except ImportError:  # OR-Tools not installed in this environment
    HAS_ORTOOLS = False

logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
log = logging.getLogger("hybrid_router")


class RoutingSolverError(Exception):
    """Raised when neither the OR-Tools solver nor the greedy fallback can
    produce a route that satisfies every site's access window."""
    def __init__(self, reason: str):
        super().__init__(f"[ROUTING_INFEASIBLE] {reason}")
        self.reason = reason


# Travel-time matrix in minutes, of the shape the Google Maps Platform Distance
# Matrix API returns for a depot plus three tower sites. Row/column order:
# DEPOT, TWR-8842, TWR-4471, TWR-6120.
DISTANCE_MATRIX_MIN = [
    [0, 22, 35, 41],
    [22, 0, 18, 30],
    [35, 18, 0, 20],
    [41, 30, 20, 0],
]
SITE_IDS = ["DEPOT", "TWR-8842", "TWR-4471", "TWR-6120"]

# (earliest_minute, latest_minute) lease access windows, measured from depot start.
TIME_WINDOWS_MIN = {
    "TWR-8842": (10, 90),
    "TWR-4471": (40, 120),
    "TWR-6120": (60, 150),
}
SERVICE_MIN = {"TWR-8842": 45, "TWR-4471": 45, "TWR-6120": 45}


def solve_with_ortools(depot_start: datetime):
    """Exact VRPTW solve via OR-Tools when the package is available."""
    manager = pywrapcp.RoutingIndexManager(len(SITE_IDS), 1, 0)
    routing = pywrapcp.RoutingModel(manager)

    def time_cb(from_idx, to_idx):
        f, t = manager.IndexToNode(from_idx), manager.IndexToNode(to_idx)
        return DISTANCE_MATRIX_MIN[f][t]

    transit_idx = routing.RegisterTransitCallback(time_cb)
    routing.SetArcCostEvaluatorOfAllVehicles(transit_idx)
    routing.AddDimension(transit_idx, 60, 24 * 60, False, "Time")
    time_dim = routing.GetDimensionOrDie("Time")
    for site_id, (early, late) in TIME_WINDOWS_MIN.items():
        idx = manager.NodeToIndex(SITE_IDS.index(site_id))
        time_dim.CumulVar(idx).SetRange(early, late)

    params = pywrapcp.DefaultRoutingSearchParameters()
    params.first_solution_strategy = (
        routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC
    )
    solution = routing.SolveWithParameters(params)
    if solution is None:
        raise RoutingSolverError("OR-Tools returned no feasible solution")

    order, index = [], routing.Start(0)
    while not routing.IsEnd(index):
        node = manager.IndexToNode(index)
        if SITE_IDS[node] != "DEPOT":
            order.append(SITE_IDS[node])
        index = solution.Value(routing.NextVar(index))
    return order


def solve_greedy_fallback(depot_start: datetime):
    """Pure-Python nearest-feasible-neighbor VRPTW approximation, used when
    OR-Tools is not installed. Enforces the same lease windows the exact
    solver would, but explores no alternatives once a site is rejected."""
    remaining = set(SITE_IDS) - {"DEPOT"}
    order, clock_min, current = [], 0, "DEPOT"
    while remaining:
        feasible = []
        for site_id in remaining:
            leg = DISTANCE_MATRIX_MIN[SITE_IDS.index(current)][SITE_IDS.index(site_id)]
            arrival = clock_min + leg
            early, late = TIME_WINDOWS_MIN[site_id]
            if early <= arrival <= late:
                feasible.append((leg, site_id, arrival))
        if not feasible:
            raise RoutingSolverError(
                f"no remaining site is reachable inside its window from {current} at t={clock_min}"
            )
        leg, site_id, arrival = min(feasible)
        order.append(site_id)
        clock_min = arrival + SERVICE_MIN[site_id]
        current = site_id
        remaining.discard(site_id)
    return order


def seal_route(order, depot_start: datetime) -> str:
    payload = json.dumps(
        {"depot_start": depot_start.isoformat(), "order": order}, sort_keys=True
    )
    return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:16]


def build_route(depot_start: datetime):
    engine = "ortools" if HAS_ORTOOLS else "greedy_fallback"
    log.info("solving route engine=%s sites=%d", engine, len(SITE_IDS) - 1)
    order = (
        solve_with_ortools(depot_start)
        if HAS_ORTOOLS
        else solve_greedy_fallback(depot_start)
    )
    route_hash = seal_route(order, depot_start)
    log.info("route sealed engine=%s order=%s hash=%s", engine, order, route_hash)
    return order, route_hash


if __name__ == "__main__":
    depot_start = datetime(2026, 7, 3, 6, 30, tzinfo=timezone.utc)
    stops, digest = build_route(depot_start)
    print(f"engine={'ortools' if HAS_ORTOOLS else 'greedy_fallback'} stops={stops} hash={digest}")

Verification & Expected Output

Save the block as hybrid_router.py and run python3 hybrid_router.py in an environment without OR-Tools installed. You should see exactly:

text
INFO solving route engine=greedy_fallback sites=3
INFO route sealed engine=greedy_fallback order=['TWR-8842', 'TWR-4471', 'TWR-6120'] hash=64ea5ccc628a75c4
engine=greedy_fallback stops=['TWR-8842', 'TWR-4471', 'TWR-6120'] hash=64ea5ccc628a75c4

Trace the logic: from the depot at t=0, TWR-8842 is 22 minutes away and its window opens at minute 10, so arrival at 22 is feasible — it’s also the only site reachable in-window this early, since TWR-4471 (35 minutes out, window opens at 40) and TWR-6120 (41 minutes out, window opens at 60) both arrive before their windows open. After a 45-minute service block the clock reads 67; from TWR-8842, TWR-4471 is 18 minutes away (arrival 85, inside its 40–120 window) and TWR-6120 is 30 minutes away (arrival 97, inside its 60–150 window) — both feasible, so the shorter leg wins. The clock reaches 130 after servicing TWR-4471, and the final leg to TWR-6120 (20 minutes) lands arrival at exactly 150, the last legal minute of its window. Run the same script with pip install ortools present and HAS_ORTOOLS flips to True; the exact solver should return the same order here because the greedy sweep already happens to be optimal for this small instance, but the log line will read engine=ortools instead. If you see RoutingSolverError raised from the fallback, the most common cause is a travel-time value that no longer matches the window boundaries — check that the matrix and the windows were captured from the same Distance Matrix API snapshot.

Gotchas & Edge Cases

  • Live traffic breaks reproducibility. A Distance Matrix API call made at 07:00 and the same call made at 07:20 can return meaningfully different transit times once traffic conditioning kicks in. If OR-Tools solves against whichever matrix happened to be in memory at solve time, two runs of the “same” route can diverge — always hash and persist the matrix alongside the resulting route, exactly as the audit hash does for the final stop order, so an auditor can reconstruct which inputs produced which route.
  • Element quota limits force batching. The Distance Matrix API caps requests at 100 elements (25 origins by 25 destinations, or fewer combinations that still multiply to 100). A regional dispatch covering 40 towers in one matrix call will be rejected outright; chunk the origin/destination sets and stitch the sub-matrices together before handing the combined matrix to OR-Tools, rather than assuming one call covers a full day’s fleet.
  • OR-Tools constraints are strict integers. The AddDimension time dimension and window bounds in this example are all in whole minutes; mixing units (seconds from one source, minutes from another) produces a model that solves “successfully” to a nonsensical schedule rather than raising an error. Normalize every input matrix and window to the same unit before it reaches the solver, the same discipline the sibling routing guide applies to its travel-time lookups.

FAQ

Can OR-Tools compute travel times itself, without Google Maps?

Yes, but only as straight-line or user-supplied estimates — OR-Tools has no built-in road network, traffic model, or geocoding. It is common to bootstrap with a Haversine (straight-line distance divided by an assumed average speed) matrix for early testing, then swap in a real Distance Matrix API matrix once accuracy against actual drive times matters, without changing anything else in the solver configuration.

Does the Google Maps Platform have any constraint-solving ability of its own?

Only a limited form: the Routes API can reorder a small set of intermediate waypoints to minimize total drive time for a single vehicle, but it has no concept of per-stop time windows, technician skill requirements, or crew capacity limits, and it cannot reason across multiple vehicles at once. For tower dispatch, where a lease access window or a municipal curfew can make an otherwise-shorter route illegal, that limited reordering is not a substitute for a dedicated VRPTW solve.

Which tool should a small operation start with?

If a crew covers only a handful of towers a day and the failure mode you are avoiding is illegal-window arrivals, the sweep in Optimizing technician routes for multi-site maintenance windows is often enough on its own, using rough or cached travel estimates. OR-Tools earns its complexity once site counts climb into the dozens or windows overlap heavily enough that a single deterministic pass starts leaving feasible routes undiscovered; the Maps Platform earns its cost once travel-time accuracy, not just legality, starts driving missed windows.

Related pages