# Caching Geocoding Results Source: https://docs.placematic.com/architecture/caching-geocoding-results Addresses do not move. Normalization as a cache-hit multiplier, what invalidation actually means here, and the privacy obligation a permanent geocode cache creates. # Caching Geocoding Results Buildings are stationary. This is the entire architectural insight, and most systems ignore it. Re-geocoding a customer's address on every order is not a small inefficiency. In many production systems it is the single largest line on the location invoice, and it is fixed with a database column. ## The problem statement A geocode is a pure function of an address string. The same input produces the same output, indefinitely, on any platform. Systems call it repeatedly anyway, because: * The geocoding call sits inside the order flow, where it was convenient to write * Nobody measured the cache hit rate, because there is no cache * The address string varies — `123 Main St` and `123 Main Street` look different to a hash map * Someone worried about staleness and chose safety over economics The last one deserves attention, because it is the only reasonable objection and it is wrong. ## The decision **What is the cache key, what is the TTL, and where does it live?** Get the key wrong and your hit rate collapses. Get the TTL wrong and you pay for stability you already had. ## Recommended architecture ```mermaid theme={null} flowchart TB IN[Raw address string] NORM[Normalize
trim, case-fold,
expand abbreviations,
strip punctuation] KEY[Cache key =
hash normalized] L1[(L1: Redis
hot addresses
TTL: days)] L2[(L2: PostgreSQL
durable record
TTL: none)] API[Geocoding API] STORE[Store: coords,
normalized address,
confidence score,
result type,
geocoded_at,
map_release] IN --> NORM --> KEY KEY --> L1 L1 -->|miss| L2 L2 -->|miss| API --> STORE --> L2 L2 -->|hit| L1 L1 -->|hit| OUT[Return] STORE --> OUT ``` Two tiers exist for different reasons. **L2 is the system of record and it never expires.** L1 is a latency optimization and it may. If you build only one tier, build L2. A durable database record with coordinates, confidence, and a timestamp is worth more than a Redis instance, because it survives a restart and it is queryable. ## Normalization is the cache-hit multiplier This is where the leverage is, and it costs nothing. Before hashing, normalize: * **Trim and collapse whitespace** * **Case-fold** — `Main St` and `MAIN ST` * **Expand abbreviations** — `St` → `Street`, `Ave` → `Avenue`, `Apt` → `Apartment` * **Strip or standardize punctuation** * **Normalize unit designators** — `#4`, `Unit 4`, `Apt 4` * **Canonicalize postal codes** — `SW1A1AA` and `SW1A 1AA` Instrument your cache hit rate *before* and *after* normalization. Teams routinely discover that half their "misses" were the same address written three ways. That is a free doubling of hit rate with no API involved. For international addresses, `libpostal` handles parsing and normalization better than a regex you will regret. It is self-hosted, removes normalization from your API bill entirely, and does not geocode. **Normalize at write time, not at read time.** Store the normalized form. Query against it. ## What invalidation means here The instinct is a TTL. The instinct is wrong. An address's coordinates change when one of three things happens: 1. **The map data changed.** A new subdivision, a re-surveyed parcel, a corrected house number. 2. **The address itself changed.** Renumbering, street renaming, municipal boundary change. 3. **Your confidence threshold changed.** A match you previously accepted, you no longer would. None of these correlate with elapsed time. A thirty-day TTL invalidates a stable rooftop match for a building that has stood since 1904, and does nothing about the subdivision that opened yesterday. A time-based TTL on geocoding is cargo-cult caching. It costs money and improves nothing. Invalidate on events, not on clocks. **Invalidate on:** * **Map release.** Store the map release version alongside the geocode. When a significant release ships, selectively re-geocode records below a confidence threshold, or records in regions with known changes. Not everything. * **Low confidence.** Records that fell below threshold should be in an exception queue, not a cache. * **Explicit correction.** A customer fixes their address; you re-geocode that record. * **Failed delivery.** An operational signal that the coordinate is wrong, regardless of what the confidence score said. **Store `geocoded_at` and the map release** so that selective re-geocoding is possible. Without them your only options are "everything" and "nothing." ## What to store Coordinates alone are insufficient and dangerous. | Field | Why | | ----------------------- | ------------------------------------------------------------------------------ | | `latitude`, `longitude` | The answer | | `normalized_address` | The cache key, and the canonical form | | `raw_address` | What the user typed. You will need it. | | `confidence_score` | Without it, a city-centroid fallback is indistinguishable from a rooftop match | | `result_type` | Address or place. A shopping centre is not a street address. | | `geocoded_at` | Enables selective re-geocoding | | `map_release` | Enables explaining why the answer changed | **Storing a coordinate without its confidence score converts a probabilistic estimate into a fact.** Every downstream consumer will treat it as ground truth. A low-confidence fallback silently corrupts revenue maps, routes vehicles to centroids, and produces failed deliveries. This is the most damaging thing you can do with a geocoding result. ## Redis versus database They solve different problems and the answer is usually both. **PostgreSQL (or your primary store)** * Durable, survives restart * Queryable — "how many addresses have confidence below 0.8" * Joinable to the customer or order record * Sits naturally next to PostGIS geometry * Slower per lookup, irrelevant at these volumes **Redis** * Sub-millisecond * Useful when the geocode is on a hot path (autocomplete resolution, checkout) * Ephemeral — treat as a cache, never as a record * Justified when the same small set of addresses is resolved constantly **Do not use Redis as the system of record.** An eviction is a re-geocode you paid for once already. For most systems, a well-indexed table with the address stored against the customer record *is* the cache. The lookup is a join you were doing anyway. ## Privacy and GDPR A permanent geocode cache is a permanent record of where people live. This is not a footnote. **Coordinates are personal data.** Under GDPR, a coordinate that identifies a residence is personal data whether or not a name is attached. The same holds under most comparable regimes. **Right to erasure applies to the cache.** When a customer requests deletion, the geocode cache is not exempt because it is "just a cache." It contains their address and its coordinates. Delete it. **This creates a design tension.** A shared, global, permanent geocode cache keyed on address is operationally ideal and a compliance problem: deleting one customer's data means finding their address in a cache shared across every customer who lives at the same address. Two workable resolutions: **Separate the address-to-coordinate mapping from the person-to-address mapping.** The coordinate for `123 Main Street, Chicago` is a fact about the world, not about a person. The fact that Jane Smith lives there is personal data. Store them separately. Delete the second; keep the first. This is clean, defensible, and it is what most mature systems do. **Or key the cache per-tenant and accept the lower hit rate.** In multi-tenant systems this may be required anyway — see below. In a **multi-tenant** system, a shared geocode cache is a data leak. Tenant A's customer list is inferable from cache hit patterns and timing. The savings were never worth it. Key every cache by tenant. See [Multi-Tenant Location Platform](/architecture/multi-tenant-location-platform). **Retention.** A geocode cache that never expires is a retention policy that says "forever." Someone in your organization has committed to something shorter. Reconcile them. **Data residency.** If addresses cross jurisdictions, so do their coordinates. ## Scaling considerations **The cache grows with distinct addresses, not with orders.** A million orders from eighty thousand customers is eighty thousand records. This is a small table. **Hit rate approaches 1 as the customer base matures.** New addresses arrive at a trickle and are geocoded in real time. The batch job runs once, at migration, and then rarely. **Deduplicate before batch geocoding.** A raw order export contains enormous repetition. Geocoding four million rows containing nine hundred thousand distinct addresses bills for four million. See [High-Volume Geocoding](/architecture/high-volume-geocoding). **Index on the normalized address hash.** Not on the raw string. **Coordinate rounding for reverse geocoding.** GPS jitter defeats exact-match caching. Round to roughly five decimal places — about one metre — before lookup. A vehicle idling at a depot generates hundreds of pings within a metre of each other. One cache entry. ## Cost implications **Instrument cache hit rate before you instrument anything else.** A team with a 12% hit rate does not have a pricing problem. They have a caching problem, and it is cheaper to fix than to migrate. Present the hit rate to whoever is asking about the invoice; it will reframe the conversation. The compounding wins, in order: 1. **Any cache at all.** Zero to something. 2. **Normalization.** Something to nearly everything. 3. **Deduplication before batch.** Removes the repetition you were paying for. 4. **Batch for the backfill.** Cheaper per record than real-time. 5. **Debounced autocomplete.** Stops billing per keystroke. Geocoding-heavy workloads see the largest savings when moving platforms — but only after the call pattern is fixed. **Migrating an uncached implementation moves the waste to a cheaper meter.** See [Reducing Google Maps Costs](/use-cases/reducing-google-maps-costs). ## Alternative architectures **No cache, with a validation vendor.** If you use a USPS-certified address validation service, it may return coordinates as a byproduct, and your call volume is bounded by new addresses anyway. Legitimate, and it answers a different question — does this address *exist* — that a geocoder does not. **Precomputed reference dataset.** Load an open address dataset — OpenAddresses, national postal files — into PostGIS and resolve locally. Zero per-call cost, coverage and freshness are yours to maintain. Realistic for a bounded operating area. **Cache in the client.** Do not. Coordinates for addresses are not the browser's business, and a client cache is neither durable nor deletable. **Geocode on write, never on read.** The strongest version of this pattern. An address entered into your system is geocoded once, at entry, before it is committed. Nothing downstream ever calls a geocoder. Every read is a column. ## Common mistakes **No cache.** The dominant one. **Caching the raw address string.** Half your hits become misses. **Time-based TTL.** Invalidating stability, ignoring change. **Storing coordinates without the confidence score.** **Redis as the system of record.** **A shared cache across tenants.** **Not deduplicating before batch geocoding.** **Geocoding inside the order flow** rather than at address creation. **No `geocoded_at` or map release version**, making selective re-geocoding impossible. **Treating the cache as exempt from erasure requests.** **No exception queue** for low-confidence matches. They enter the cache as truth. **Re-geocoding on every read "to be safe."** Safety is a confidence score, not a fresh call. ## Production checklist * [ ] Cache exists, keyed on normalized address, hit rate instrumented * [ ] Normalization applied at write time, before hashing * [ ] Confidence score and result type persisted alongside coordinates * [ ] `geocoded_at` and map release version stored * [ ] No time-based TTL; invalidation triggered by map release, low confidence, correction, or operational failure * [ ] Durable store is the system of record; Redis, if present, is treated as ephemeral * [ ] Cache keyed by tenant in multi-tenant systems, verified by a cross-tenant test * [ ] Address-to-coordinate mapping separated from person-to-address mapping, or erasure path defined * [ ] Retention policy reconciled with "permanent cache" * [ ] Exception queue for sub-threshold matches, with an owner * [ ] Input deduplicated before any batch job * [ ] Reverse-geocode lookups round coordinates before cache access ## Related guides Endpoint selection, confidence scores, autocomplete versus autosuggest. The job lifecycle for the initial backfill. Coordinate rounding and the caching that makes telematics viable. Millions of addresses, queues, and idempotency. ## Related use cases [Address Validation](/use-cases/address-validation) · [Reducing Google Maps Costs](/use-cases/reducing-google-maps-costs) · [Vehicle Tracking](/use-cases/vehicle-tracking) · [Location Intelligence](/use-cases/location-intelligence) ## HERE documentation * [Geocoding & Search v7](https://www.here.com/docs/category/geocoding-search-api-v7) * [Batch API v7](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/batch-api-quick-start.html) *** Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Routing vs Matrix: Choosing the Right Primitive Source: https://docs.placematic.com/architecture/choosing-routing-vs-matrix Why engineers reach for point-to-point routing when they need a matrix, what it costs asymptotically, and the decision tree that prevents it. # Routing vs Matrix: Choosing the Right Primitive The most expensive line of code in fleet software is a `for` loop. for stop in stops: route = routing\_api(depot, stop) travel\_times\[stop] = route.duration It is correct. It passes review. It works in staging with five stops. At two hundred depots and three thousand stops it is six hundred thousand API calls, and it should have been one. ## The problem statement Routing and Matrix Routing return overlapping information, which is why they are confused. Both give you a duration between two points. They answer different questions: | | Routing | Matrix Routing | | ---------- | ----------------------------------------- | ---------------------------------------------------------------- | | Question | How do I get from A to B? | What are the travel costs between all of these and all of those? | | Returns | A path — geometry, instructions, sections | A table — durations, distances, consumptions | | Calls | 1 per origin-destination pair | 1 per matrix | | Complexity | O(n·m) for an n×m table | O(1) | **Routing returns a path. Matrix returns a cost table.** If you are discarding the path, you asked the wrong question. The tell: you are calling a routing API and reading only `duration` or `length` from the response. The polyline, the sections, the turn-by-turn actions — all computed, all transmitted, all discarded. You paid for a path you did not want. ## Why engineers misuse Routing This is worth understanding, because "just use matrix" is advice everyone has heard and many teams still get wrong. **It is discoverable first.** Routing is the obvious API. It is what you reach for when you need a travel time. Matrix requires knowing it exists. **It works.** The loop returns correct answers. There is no error, no warning, no degradation. It is not a bug in any conventional sense. **The failure is asymptotic, not local.** At five stops the loop is imperceptible. The problem is a growth rate, and growth rates are invisible in a code review of the diff that introduced them. **Matrix is asynchronous at scale.** Routing is a simple synchronous request. Matrix, for large problems, means submit-poll-retrieve, which is more code and more state. Engineers optimizing for the shortest path to a working feature choose synchronous. **The response shape is unfamiliar.** Matrix returns flat, row-major arrays — `travelTimes: [73, 1231, 983, 400]` for a 2×2 — not nested objects. Indexing it correctly requires thought. None of these are unreasonable. They are why the mistake is systemic rather than careless. Naming it explicitly in your architecture review is worth more than any amount of documentation. ## The decision ```mermaid theme={null} flowchart TD START{What are you
building?} START -->|A path a vehicle
will follow| ROUTE[Routing API] START -->|A number in a table| TABLE{How many
origin-destination
pairs?} START -->|A sequence of stops
to visit| TOUR[Tour Planning] TABLE -->|One| ROUTE TABLE -->|More than one| MATRIX[Matrix Routing] ROUTE --> CHECK{Are you in
a loop?} CHECK -->|Yes| MATRIX CHECK -->|No| DONE[Correct] TOUR --> NOTE[Tour Planning consumes
a cost table internally.
Do not build one for it.] ``` Three primitives. One rule that catches almost every error: **If you are writing a loop around a routing call, stop.** You are building either a cost table (Matrix) or an itinerary (Tour Planning). Both exist. Both are cheaper. ## Cost implications The arithmetic is not subtle. An n×m cost table costs **n·m routing calls** or **1 matrix call**. For a 20-depot, 500-stop assignment problem, that is 10,000 calls versus 1. Matrix operations are priced differently from routing, so the ratio is not exactly 10,000:1 in currency. It does not need to be. It is not a rate difference. It is a complexity difference, and no pricing negotiation closes it. Do not attempt to fix this with a volume tier discount. You are not paying too much per call. You are making the wrong number of calls. **Where the loop hides:** * Nearest-driver assignment — one routing call per available driver * Store locator ranking — one per candidate store * Territory design — one per rep-base × unit centroid * Delivery zone economics — one per zone per candidate * Site selection — one per demand cell per candidate site * Freight rating — one per lane, per quote, uncached Every one of these is a matrix. ## Performance Latency, not just cost. **Serial loop:** n·m round trips. Each carries network latency. A 40-call loop at 120ms per call is 4.8 seconds before any computation. **Parallel loop:** you have now built a fan-out that will trip rate limits, and you have converted a cost problem into a `429` problem. **Matrix (synchronous):** one round trip, one latency. **Matrix (asynchronous):** submit, poll, retrieve. Higher wall-clock time for small problems. Vastly lower for large ones, and it does not block a thread. For very small tables — a 1×3 store ranking — the serial loop may genuinely be faster in wall-clock terms than a matrix round trip. It is still the wrong primitive, because the code that ranks three stores today will rank three hundred next year, and nobody will revisit it. ## Scaling considerations **Sync and async are different products.** Matrix Routing v8 supports both, with different capabilities and different size ceilings depending on mode — flexible, region, or profile. The OpenAPI specification at `https://matrix.router.hereapi.com/v8/openapi` is authoritative. Read it before architecting against a ceiling. Matrix size limits vary by mode, by sync-versus-async, and by entitlement. Published figures are frequently quoted out of context. Do not design against a number from a comparison table. Confirm it against the specification and your contract. **Async means state.** Submit, receive a `matrixId` and a `statusUrl`, poll, retrieve. That state must survive a process restart, or a crashed worker resubmits a job you already paid for. Follow the `statusUrl` HERE returns. Do not construct it. **Flat arrays are row-major.** A 2×2 matrix returns `travelTimes: [73, 1231, 983, 400]`. Index as `travelTimes[originIndex * numDestinations + destinationIndex]`. Get this wrong and you get a **silently transposed matrix**. Every travel time is plausible. Every assignment is wrong. Nothing throws. Write exactly one indexing helper for the flat arrays, unit-test it against a hand-computed 2×3, and never index the array anywhere else. This is the highest-value five lines of code in a matrix integration. **Cache on the hash of the input set.** Depot locations do not move. If neither origins, destinations, nor departure time changed, the matrix did not change. **Request only what you consume.** Asking for `consumptions` when nothing downstream reads them inflates the response for no benefit. ## Alternative architectures **Build your own cost table from cached routing calls.** Viable when the origin-destination pairs are few, stable, and repeated — a freight rating engine with fixed lanes, for instance. You are trading matrix calls for a cache. Legitimate. **Precompute the matrix nightly.** Where the input set is stable, a matrix is a materialized view. Compute it on a schedule, serve from a table. This is the correct architecture for territory management and site selection, where the analyst iterates and the geometry does not change. **Use straight-line distance to shortlist, matrix to rank.** For a store locator with 2,000 locations, do not matrix all of them. `ST_DWithin` narrows to ten candidates for free; matrix ranks those ten correctly. **Straight-line distance is a bad ranker and an excellent filter.** **Bring your own solver, fed by matrix.** If you need custom optimization objectives, the matrix is your interface to reality. This is a legitimate build; approximating the matrix with routing loops is not. ## Common mistakes **Looping routing to build a cost table.** The subject of this page. **Parallelizing the loop.** You have converted spend into `429`s. **Indexing the flat matrix array as if it were nested.** Silently transposed. **Constructing the `statusUrl`** rather than following the one returned. **Blocking a request thread on an async matrix job.** Timeouts, retries, duplicate submissions, double billing. **Losing the `matrixId` on worker restart.** Resubmission is a full recharge. **Building a truck matrix in car mode.** Matrix accepts truck parameters. A truck cost table built from car travel times produces wrong assignments, confidently. **Using matrix where you needed a path.** The inverse error. Matrix returns no geometry. If a driver follows it, you needed routing. **Approximating Tour Planning with a matrix and a nearest-neighbour heuristic.** The solver exists, handles capacity, time windows, and priorities, and is included in the HERE Base Plan. **Architecting against a size ceiling from a blog post.** ## Production checklist * [ ] Every routing call audited: is it inside a loop? * [ ] Cost-table workloads identified and moved to Matrix Routing * [ ] Single indexing helper for flat result arrays, unit-tested against a known 2×3 * [ ] Async jobs: `matrixId` persisted before the first poll * [ ] `statusUrl` followed verbatim, never constructed * [ ] Matrix cached on a hash of the input set * [ ] Truck parameters passed when the fleet is commercial * [ ] Only consumed fields requested * [ ] Size ceilings confirmed against the OpenAPI specification and your entitlement * [ ] Rate-limit behaviour on submission handled with backoff, not retries in a tight loop ## Related guides Sync versus async, region definitions, and the flat array. Transport modes, `return` fields, and the `200` that is not success. When the question is a sequence, not a table. Where this pattern sits among the others. ## Related use cases [Fleet Routing](/use-cases/fleet-routing) · [Store Locator](/use-cases/store-locator) · [Territory Management](/use-cases/territory-management) · [Reducing Google Maps Costs](/use-cases/reducing-google-maps-costs) ## HERE documentation * [Matrix Routing API v8](https://www.here.com/docs/category/matrix-routing-api-v8) * [Matrix Routing OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi) * [Routing API v8](https://www.here.com/docs/category/routing-api-v8) *** Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Cost Optimization Patterns Source: https://docs.placematic.com/architecture/cost-optimization-patterns Seven patterns, ranked by the order of magnitude they move. The arithmetic, the failure modes, and why the last three matter more than the first four. # Cost Optimization Patterns Location API bills do not grow linearly with a business. They grow with whatever the system calls per unit of something. Get that unit wrong and no rate negotiation saves you. Get it right and the bill becomes a small, flat, forecastable line that grows with locations rather than with traffic. This page is a taxonomy of the patterns that move it, ordered by the magnitude they move. Read this alongside [Reducing Google Maps Costs](/use-cases/reducing-google-maps-costs), which is a *process* — instrument, fix, model. This page is the *catalogue* the process draws from. ## The problem statement Almost every expensive location system has the same shape: * An API call attached to a unit of work that is too small * A computation performed at request time that should have been performed once * A latency guarantee purchased for a workload where nobody is waiting Everything below is a variation on those three. ## The ordering principle Patterns are ranked by the **exponent** they change, not the constant. | Rank | Pattern | Moves the bill by | Effort | | ---- | -------------------------- | ------------------- | ------- | | 1 | Change the unit of work | Orders of magnitude | Low | | 2 | Choose the right primitive | Orders of magnitude | Low | | 3 | Precompute and materialize | Unbounded → bounded | Medium | | 4 | Cache | Large constant | Low | | 5 | Deduplicate | Large constant | Trivial | | 6 | Batch | Rate difference | Medium | | 7 | Trim the request | Small constant | Trivial | Teams routinely start at rank 7. Setting `return` fields correctly is worth doing and will never save a platform whose architecture calls a geocoder per GPS packet. *** ## Pattern 1: Change the unit of work **The single highest-leverage change available, and it is not a caching problem.** Systems call APIs per unit. The default unit is almost always too small. | Wrong unit | Right unit | Ratio | | ---------- | ---------------- | ------------------------------ | | GPS packet | Detected stop | \~250:1 for a 12-stop shift | | Keystroke | Debounced query | \~8:1 on a typical address | | Page load | User action | Unbounded | | Order | Distinct address | \~5:1 in mature customer bases | | Trip point | Trip | \~2,000:1 | A vehicle emitting a position every ten seconds over a nine-hour shift produces 3,240 packets. Across 200 vehicles: 648,000 per day. **Any API call attached to a packet multiplies by that number.** The address is needed when a human looks at a screen, or when a stop is detected. Not at 3:47:20am on I-80. **Diagnostic:** instrument the ratio of API calls to business events. Reverse-geocode calls per detected stop should be near 1. Routing calls per dispatch should be near the number of legs. Geocodes per order should approach zero. This ratio is the most valuable number in your observability stack. Total call volume grows with the business; the ratio should not. A ratio that doubles overnight is a regression a deploy introduced, and it is visible hours before it appears on an invoice. *** ## Pattern 2: Choose the right primitive Three primitives look interchangeable and are not. ```mermaid theme={null} flowchart TD Q{What do you
actually need?} Q -->|A path a vehicle
will follow| R[Routing] Q -->|A table of costs| M{How many pairs?} Q -->|A sequence of stops| T[Tour Planning] M -->|One| R M -->|More than one| MX[Matrix Routing] R --> L{In a loop?} L -->|Yes| MX L -->|No| OK[Correct] ``` **An n×m cost table costs n·m routing calls or 1 matrix call.** A 20-depot, 500-stop assignment problem is 10,000 calls or 1. That is a complexity difference, not a rate difference. No pricing tier closes it. **Where the loop hides:** nearest-driver assignment, store locator ranking, territory design, freight rating, site selection, delivery zone economics. All matrices. **The inverse error:** using a solver where a lookup would do, or matrix where you needed geometry. Matrix returns no path. If a driver follows it, you needed routing. See [Routing vs Matrix](/architecture/choosing-routing-vs-matrix). *** ## Pattern 3: Precompute and materialize **This pattern converts an unbounded cost into a bounded one.** That is a stronger statement than "it saves money." Certain artifacts are expensive to compute, cheap to store, and stable for months: * Drive-time isolines around fixed locations * Distance matrices between fixed depots and stable destination sets * Lane distances for freight rating * Territory polygons * Trade areas Compute them on a schedule. Store them in PostGIS. Query them locally, forever. **The arithmetic:** A 400-store network with three delivery bands is **1,200 isoline calls per quarter.** Not per day. Not per customer. The cost is bounded by store count — a business number you already know. The same system, computing isolines at checkout, costs one call per session. That is unbounded, it scales with your success, and it is why the pattern matters. If your architecture calls an external service to answer "is this point inside this polygon," you have built a spatial database with an API bill attached. Containment is a geometry test. It requires no road network, no traffic data, no routing engine. **Diagnostic:** for each API call in a request path, ask *what would have to change for this answer to differ?* If the answer is "a new store opens," it is a materialization candidate. **Failure modes:** stale artifacts. Version them. Recompute on events — a location opens, a service promise changes, a significant map release ships — not on a timer. See [Delivery Zones](/use-cases/delivery-zones) and [Catchment Area](/guides/catchment-area). *** ## Pattern 4: Cache Buildings are stationary. This is the whole insight. **Normalization is the multiplier.** `123 Main St` and `123 Main Street` are the same address, and a cache keyed on the raw string treats them as different. Trim, case-fold, expand abbreviations, canonicalize postal codes — *before* hashing. Instrument the hit rate before and after. Teams routinely find that half their misses were the same address written three ways. Free doubling. **Invalidate on events, not clocks.** A thirty-day TTL on a geocode invalidates a stable rooftop match for a building that has stood since 1904, and does nothing about the subdivision that opened yesterday. Time-based TTL on geocoding is cargo-cult caching: it costs money and improves nothing. Invalidate on map release, low confidence, explicit correction, or an operational failure signal such as a failed delivery. **Round before caching coordinates.** GPS jitter defeats exact-match lookup. Round to roughly five decimal places — about one metre. A vehicle idling at a depot generates hundreds of pings within a metre of each other. One entry. **Different TTLs for different things.** Route geometry between fixed points is stable for hours. The ETA is not. Cache them separately or you serve stale arrival times to drivers. **The multi-tenant caveat:** a shared geocode cache is a side channel. Key by tenant. See [Multi-Tenant Location Platform](/architecture/multi-tenant-location-platform). See [Caching Geocoding Results](/architecture/caching-geocoding-results). *** ## Pattern 5: Deduplicate The cheapest pattern on this page, skipped constantly. A raw order export containing four million rows may contain nine hundred thousand distinct addresses. Geocoding it bills four million. **Normalize first, then deduplicate.** Deduplicating before normalization catches only exact string matches, which is most of the repetition and not all of it. **Filter what you already have.** `WHERE geocoded_at IS NULL`. On the second run this is most of the table. Run the deduplication stage and count the rows that remain. Present that number before you present a timeline. It is usually a fifth of what you were handed, and it changes the conversation with whoever asked. **Request deduplication at the edge** is the real-time analogue. Two concurrent requests for the same geocode should produce one upstream call. A simple in-flight request map handles it. *** ## Pattern 6: Batch Same data, different product, different rate. **The tell:** if the result is written to a database rather than rendered to a screen, it should have been batched. Nothing waits for nightly address normalization. Nothing waits for historical trip matching. Nothing waits for a partner data import. Paying real-time rates for latency nobody consumes is the second-largest avoidable cost in this domain. **Batch is a job lifecycle, not a bulk endpoint.** Create, start, poll, retrieve errors, retrieve results, delete. Persist the job ID *before* polling, or a worker restart resubmits a job you already paid for. **Three status codes that are not errors:** | Response | Meaning | | ------------------------ | -------------------------- | | `429` on job start | Concurrency limit. Queue. | | `204` on errors endpoint | Zero errors. Celebrate. | | `404` on results | Job has not succeeded yet. | Code that treats any non-`200` as failure will log a successful job as broken every night until someone reads the specification. See [High-Volume Geocoding](/architecture/high-volume-geocoding) and [Batch Geocoding](/guides/batch-geocoding). *** ## Pattern 7: Trim the request Small, free, and worth doing after the six patterns above. **Set `return` explicitly.** Requesting `polyline,actions,instructions,turnByTurnActions` when you needed `summary` inflates payloads by orders of magnitude. Nothing consumes turn-by-turn instructions server-side. **Request only matrix fields you read.** `consumptions` is not free. **Debounce autocomplete** at 200–300ms. Undebounced, you bill once per character typed, per session, forever. **Coarsen resolution where nobody sees the difference.** A high-resolution isoline renders identically to a simplified one at the zoom level shown, and tests containment faster. **CDN in front of tiles.** Static per zoom, x, y, and style. Available on any platform. *** ## Monitoring and forecasting **Alert on calls per business event, not on total volume.** Total volume is supposed to grow. The ratio is not. Instrument: * Cache hit rate, per cache * Reverse-geocode calls per detected stop * Routing calls per dispatch * Geocodes per order * Matrix calls per assignment cycle * `429` rate, by endpoint * `403` count — should be zero; any occurrence is a licensing issue, not a bug **Forecasting is a function of business inputs, not of last month's bill.** Once the architecture is right, the cost of most surfaces is bounded by a number you already know: | Surface | Bounded by | | ------------------ | ------------------------------------------ | | Materialized zones | Location count × bands × refresh frequency | | Store locator | Sessions × cache miss rate × k | | Geocoding | Distinct new addresses per month | | Trip matching | Trips per day, not points | | Territory design | Rep count × unit count, quarterly | If a line item is large and *variable*, something in the pipeline is calling an API where it should be reading a table. Present the forecast as a function of business inputs. "Our location cost is $X per thousand new distinct addresses plus $Y per store per quarter" is a sentence a CFO can plan against. "It was \$40k last month" is not. **Model both commercial models.** Call-volume pricing suits SaaS platforms whose calls track customer count. Asset-based pricing suits fleet operators with a countable vehicle population and unpredictable call volume — a dispatcher rerouting 200 trucks forty times a day is punished by call-volume pricing for operational diligence. Asset-based availability depends on contract tier. Confirm before it becomes load-bearing in a business case. See [HERE Pricing Explained](/getting-started/here-pricing-explained). *** ## Where these patterns compose ```mermaid theme={null} flowchart LR subgraph Unbounded["Unbounded cost"] A[API call per
packet / keystroke /
page load] end subgraph Bounded["Bounded cost"] B[API call per
event / location /
distinct address] end subgraph Free["Zero marginal cost"] C[PostGIS query
Redis lookup
Table read] end A -->|Pattern 1: unit of work| B A -->|Pattern 2: primitive| B B -->|Pattern 3: materialize| C B -->|Pattern 4: cache| C B -->|Pattern 5: deduplicate| B B -->|Pattern 6: batch| B ``` **Patterns 1 and 2 move you from unbounded to bounded. Patterns 3 and 4 move you from bounded to free.** Patterns 5, 6, and 7 reduce the constant on whatever remains. That ordering is why a team that starts by negotiating rates and setting `return` fields ends up with a cheaper unbounded system. *** ## Common mistakes **Optimizing constants before exponents.** Trimming responses on a system that geocodes every packet. **Negotiating a rate to fix a complexity problem.** A routing loop is not expensive per call. **Time-based TTLs on geocoding.** **Caching the raw address string.** **Calling an API to answer a containment question.** **Materializing nothing, then blaming the vendor.** **Not deduplicating before batch.** **Real-time rates for overnight work.** **Alerting on total volume** rather than on calls per business event. **Shared caches in multi-tenant systems.** A leak dressed as an optimization. **Migrating before optimizing.** Moving the waste to a cheaper meter. You will save money and still be wrong. **Forecasting from last month's invoice** rather than from business inputs. **Treating `403` as retryable.** It never succeeds, and it is a commercial conversation. *** ## Production checklist * [ ] Calls-per-business-event instrumented for every high-volume surface * [ ] Reverse-geocode-to-detected-stop ratio measured and near 1 * [ ] Every routing call audited: is it inside a loop? * [ ] Cost-table workloads moved to Matrix Routing * [ ] Serviceability, catchments, and lane distances materialized in PostGIS * [ ] Nothing in a request path calls an API to answer a containment question * [ ] Geocode cache keyed on normalized address; hit rate instrumented * [ ] Normalization applied at write time, before hashing * [ ] Invalidation triggered by events, not by TTL * [ ] Coordinates rounded before reverse-geocode cache lookup * [ ] Input deduplicated before every batch job * [ ] Latency-tolerant workloads moved to batch * [ ] Batch job IDs persisted before polling; `429`/`204`/`404` handled correctly * [ ] `return` fields explicit on every routing call * [ ] Autocomplete debounced at 200–300ms * [ ] CDN in front of tiles * [ ] Caches keyed by tenant in multi-tenant systems * [ ] Alerts on calls-per-event ratios, `429` rate, and any `403` * [ ] Forecast expressed as a function of business inputs, not last month's bill * [ ] Both commercial pricing models modelled against your worst month *** ## Related guides Pattern 2, in full. Pattern 4, including normalization and privacy. Patterns 5 and 6, as a pipeline. Call volume versus asset-based, and free bundles that do not pool. ## Related use cases [Reducing Google Maps Costs](/use-cases/reducing-google-maps-costs) · [Vehicle Tracking](/use-cases/vehicle-tracking) · [Delivery Zones](/use-cases/delivery-zones) · [Geofencing](/use-cases/geofencing) · [Multi-Tenant Location Platform](/architecture/multi-tenant-location-platform) ## HERE documentation * [Matrix Routing API v8](https://www.here.com/docs/category/matrix-routing-api-v8) * [Batch API v7 limits and performance](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/limits-and-performance.html) * [Geocoding & Search v7](https://www.here.com/docs/category/geocoding-search-api-v7) *** Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Google Migration Architecture Source: https://docs.placematic.com/architecture/google-migration-architecture Dual-running, shadow comparison, and rollback — the mechanics of moving a production location system without a Sunday-night cutover. # Google Migration Architecture This page is about the mechanics of the move, not the decision to make it. The decision — whether to migrate at all, and what it costs — lives in [Reducing Google Maps Costs](/use-cases/reducing-google-maps-costs). The endpoint mapping and sequencing live in [Migrating from Google Maps](/guides/google-migration). **What follows is how you do it without a Sunday-night cutover and a Monday-morning incident.** ## The problem statement You cannot A/B test a routing engine the way you A/B test a button. The failure modes are subtle and delayed. A truck route that omits a constraint returns `200` and looks correct in a diff. A silently transposed matrix produces plausible travel times and wrong assignments. A geocode that falls back to a city centroid renders as a pin on a map. **You will not notice the regression by watching error rates.** Everything succeeds. The most dangerous migration outcome is not an outage. It is a system that works, is cheaper, and is quietly wrong in ways that surface as operational incidents six weeks later, with no clear cause. ## The decision **How do you compare two providers on production traffic without exposing users to either the comparison or the risk?** The answer is shadow-writing, and it has three parts: run both, compare offline, cut over per endpoint. ## Recommended architecture ```mermaid theme={null} flowchart TB REQ[Production request] subgraph Facade["Provider facade"] FLAG{Feature flag
per endpoint} PRIMARY[Primary provider] SHADOW[Shadow provider
async, fire-and-forget] end subgraph Compare["Offline comparison"] LOG[(Response log
both providers,
correlated by request id)] DIFF[Diff job
nightly] GROUND[(Ground truth
telematics actuals)] REPORT[Divergence report] end RESP[Response to user] REQ --> FLAG FLAG --> PRIMARY --> RESP FLAG -.->|copy| SHADOW PRIMARY --> LOG SHADOW --> LOG LOG --> DIFF GROUND --> DIFF DIFF --> REPORT ``` Three properties, all non-negotiable: **The shadow call is asynchronous and its result is discarded.** It never touches the response path. If it fails, times out, or returns nonsense, the user is unaffected. If shadow latency can affect production latency, you have built a second point of failure to test the first. **The flag is per endpoint, not per user.** Geocoding can be on HERE while routing is on Google. A rollback is one flag and one surface. Percentage rollouts across a routing engine give you two populations with different route qualities and no way to attribute an incident. **Comparison happens offline, against ground truth.** Not against each other. ## The provider facade Introduce it before you introduce HERE. interface RouteProvider: route(origin, destination, vehicle\_profile, departure\_time) -> Route matrix(origins, destinations, vehicle\_profile) -> CostTable geocode(normalized\_address) -> GeocodeResult This is unglamorous and it is where most of the migration's engineering effort actually goes. The interface must be: **Provider-agnostic in its types.** `Route` is your type, not Google's response shape and not HERE's. Coordinate order is `lat, lng` internally, converted at each adapter — HERE Routing takes `lat,lng`, GeoJSON is `lng,lat`, and this will bite you exactly once. **Explicit about the vehicle profile.** Height in centimetres, `grossWeight` in kilograms, `axleCount` including trailers. A facade that omits vehicle constraints because Google could not express them will silently drop them when HERE could. The facade is where truck constraints get lost, and August 2026 made this harder to catch, not easier. An interface derived from Google before Large Vehicle Routing had no field for vehicle height at all — obviously incomplete the moment you added HERE. An interface derived from Google's large vehicle profile has height, weight and axle count, and looks complete. The fields it cannot carry — weight per axle group, ADR tunnel category, toll cost — are exactly the ones that determine legality where regulation is tightest. Design the interface from the richer constraint set, and assert field-level parity in tests rather than trusting that a provider responded. **Honest about capability gaps.** Some methods will throw `NotSupported` on one provider. That is information, not a design flaw. ## What to compare, and against what **Not provider against provider.** Two wrong answers can agree. | Surface | Ground truth | | -------------- | --------------------------------------------------- | | Route duration | Actual driven duration from telematics | | Route distance | Actual driven distance | | Route geometry | Actual driven path, map-matched | | Geocode | A held-out set with verified rooftop coordinates | | Matrix | Sampled cells, validated against single-route calls | | Truck routes | Trap geometry — pass/fail, not a diff | **Replay real historical trips.** Take five hundred trips your fleet actually drove. Route them through both providers. Compare each against what the vehicle did. Compare the *distribution* of the residual, not the mean. A provider whose durations are unbiased on average but have twice the variance produces twice as many late deliveries. The mean will tell you nothing about that. **For truck routing, the comparison is binary.** Route a 410 cm vehicle through the 11foot8 bridge in Durham NC, Storrow Drive in Boston, and the Southern State Parkway on Long Island. Any path returned is a failure. Run the same three in `car` mode as a control — all three should route, proving the test exercises the constraint rather than failing for an unrelated reason. This is not a metric. It is a gate. ## Sequencing Migrate one surface. Stabilize. Then the next. Never two. Two independent systems change, one production incident occurs, root cause is ambiguous. You will spend more on the investigation than you saved that month. ```mermaid theme={null} flowchart LR A[1. Batch geocoding
smallest blast radius,
largest saving] --> B[2. Telematics
reverse geocoding
backend, high volume] B --> C[3. Matrix
where the routing
loops were] C --> D[4. Routing
now user-visible,
needs quality gate] D --> E[5. Autocomplete
user-facing,
be honest] E --> F[6. Rendering
most visible,
show design early] ``` **Batch geocoding first.** Nothing user-facing. If it breaks, you fix a nightly job. The saving is typically the largest single item, and it validates the facade before anything visible depends on it. **Rendering last.** Tile styling differs visibly. Your designers will file a ticket. Show them the maps before you commit, not after. ## Rollback **A migration you cannot reverse is not a migration. It is a bet.** Write the rollback procedure before the cutover, and execute it once in production during a low-traffic window, deliberately. An untested rollback is a hypothesis. Rollback must be: **One flag, one endpoint.** No deploy. No coordination. **Stateless.** If cutting over wrote data in a HERE-specific shape — a `hereId` on a place record, a matched-segment reference — rollback must not require unwinding it. Store provider-agnostic data; keep provider identifiers in a nullable side column. **Fast enough to use.** If rollback takes twenty minutes, nobody will pull the trigger during an incident. They will debug forward, badly, at 2am. **Tested.** Flip it, verify traffic moves, flip it back. On a Tuesday. ## Keep both live during cutover Two invoices for a month is cheaper than one incident. Run both providers with the shadow path active for at least one full business cycle after cutover — one month for a fleet with monthly reporting, one quarter if your compliance cycle is quarterly. You are not testing the happy path. You are waiting for the edge case that only occurs on the last Friday of a month. **Revoke the old provider's keys last, and deliberately.** A key still in a config file six months after migration is a key someone will find. ## Cost implications **The migration itself costs money.** Shadow traffic doubles your call volume for its duration. Budget for it, and say so out loud. **Model migration engineering cost against projected savings.** A payback period beyond eighteen months means the savings argument is weaker than the headline number suggests. Say that before your CFO does — it costs you one migration and buys credibility for the ones worth doing. **The meters do not map one to one.** Google bills per session where HERE bills per request, and the reverse. You cannot derive a HERE forecast from a Google invoice. Instrument per-endpoint call counts from logs first. See [Reducing Google Maps Costs](/use-cases/reducing-google-maps-costs). **Fix the call pattern before you migrate.** Cache, batch, debounce, deduplicate. Re-measure. The remaining bill is your real migration target, and now you have a clean baseline. ## Risk reduction **Instrument before you evaluate.** Per-endpoint call counts from logs, not estimates. **Optimize on Google first.** Halve the bill without a vendor change, then decide whether the rest is worth engineering time. **Prove the facade with the least visible surface.** **Gate on quality, not just cost.** A cheaper wrong route is worse than an expensive right one. **Hold the truck-routing trap tests in CI permanently**, not just during migration. A refactor that drops `vehicle[height]` from a params dict is invisible in code review. **Document what stays on Google, and why**, before someone frames a deliberate hybrid as a failed migration. ## Alternative architectures **Big-bang cutover.** Occasionally correct — a greenfield surface with no production traffic, or a system small enough that the entire diff fits in one head. Never correct for routing in a fleet system. **Percentage rollout by user.** Standard for feature flags, wrong for routing engines. You now have two populations receiving different route quality, no shared ground truth, and an incident whose blast radius is defined by a hash function. **Dual-run without shadow comparison.** Running both and comparing nothing is theatre. If you are not diffing against ground truth, you are paying twice for the illusion of caution. **Migrate by acquisition.** Some teams move only new customers to HERE and leave existing ones on Google indefinitely. This is a permanent hybrid with two code paths and two sets of bugs. It is a legitimate choice if the old population is shrinking, and a slow disaster if it is not. Decide which. **Do not migrate.** If spend after optimization is small, if your product's core value is consumer place discovery, or if the payback period is measured in years — say so. The strongest signal a docs page can send is that its author will tell you not to buy. ## Common mistakes **Migrating before optimizing.** You may be migrating waste. **Designing the facade from the narrower constraint set.** The missing fields are silent, and since August 2026 the facade looks complete while missing them. **Comparing providers against each other** instead of against ground truth. **Comparing means, not distributions.** **Shadow calls on the response path.** A second point of failure. **Percentage rollout across a routing engine.** **Migrating two surfaces simultaneously.** **Untested rollback.** **Provider-specific data written into core records**, making rollback stateful. **Treating truck-routing trap tests as a migration artifact** and deleting them afterwards. **Cutting over rendering first.** Most visible, least important. **Revoking the old keys before the first full business cycle completes.** **Presenting savings without migration cost.** ## Production checklist * [ ] Per-endpoint call counts instrumented from logs before any decision * [ ] Call pattern optimized on the incumbent; savings measured and reported * [ ] Provider facade introduced and shipped against the incumbent, before HERE is added * [ ] Facade types are yours; coordinate order converted at each adapter * [ ] Vehicle profile is a first-class field in the interface * [ ] Shadow path is asynchronous, result discarded, cannot affect production latency * [ ] Feature flag is per endpoint, not per user * [ ] Ground truth dataset assembled: historical trips with actual duration and distance * [ ] Comparison job diffs both providers against ground truth, nightly * [ ] Residual distribution reported, not just the mean * [ ] Truck-routing trap assertions in CI, with a `car`-mode control, permanently * [ ] Rollback procedure written, and executed once in production, deliberately * [ ] Provider-specific identifiers stored in nullable side columns * [ ] Both providers live for at least one full business cycle post-cutover * [ ] Migration engineering cost estimated; payback period computed and stated * [ ] Hybrid decisions — what stays on Google — documented as decisions * [ ] Old provider keys revoked last, and audited for stragglers ## Related guides Endpoint mapping, sequencing, and the mechanical differences. Where each genuinely wins, including where Google does. The trap geometry that gates the routing cutover. The system you are migrating into. ## Related use cases [Reducing Google Maps Costs](/use-cases/reducing-google-maps-costs) · [ETA Calculation](/use-cases/eta-calculation) · [Fleet Routing](/use-cases/fleet-routing) ## HERE documentation * [Routing API v8](https://www.here.com/docs/category/routing-api-v8) * [Geocoding & Search v7](https://www.here.com/docs/category/geocoding-search-api-v7) * [Matrix Routing API v8](https://www.here.com/docs/category/matrix-routing-api-v8) *** Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # High-Volume Geocoding Architecture Source: https://docs.placematic.com/architecture/high-volume-geocoding Four million addresses, one pipeline — job lifecycle versus stream, idempotency, the status codes that are not errors, and what to monitor when nothing is watching. # High-Volume Geocoding Architecture Somebody has just handed you a table with four million addresses and asked how long it will take. The answer depends almost entirely on decisions you make before you write any code, and none of them are about the geocoding API. ## The problem statement High-volume geocoding is not "geocoding, but more." It is a different system: * **Nothing is waiting.** Latency has no value. Paying real-time rates for it is waste. * **Partial failure is normal.** Some records will not resolve. All-or-nothing pipelines either discard 3.9 million good results or persist 100,000 bad ones. * **The job outlives the process.** Workers restart. Deployments happen. Resubmitting a four-million-record job bills four million records again. * **The input is repetitive.** A raw export contains the same address hundreds of times. * **The output is a permanent asset.** Do this once, correctly, and never again. The most expensive high-volume geocoding mistake is not choosing the wrong API. It is running the job twice. ## The decision **Is this a job or a stream?** A **job** has a beginning and an end, a known input set, and no consumer waiting. Backfills, migrations, partner data imports, nightly enrichment. A **stream** has unbounded arrival, and each record's latency matters to somebody. Address entry at checkout. They demand different architectures. Most systems have both and conflate them. ## Recommended architecture ```mermaid theme={null} flowchart TB subgraph Prep["Preparation — before any API call"] SRC[(Source table
4M rows)] NORM[Normalize] DEDUP[Deduplicate
→ 900k distinct] FILTER[Filter already-geocoded
WHERE geocoded_at IS NULL] end subgraph Jobs["Job orchestration"] CHUNK[Chunk into jobs] QUEUE[Job queue
respects concurrency limit] SUBMIT[Create → Start] STATE[(Job state
job_id persisted
BEFORE polling)] end subgraph Poll["Poll with backoff"] STATUS[Poll status] DONE{Terminal?} end subgraph Collect["Retrieve"] RESULTS[Retrieve results] ERRORS[Retrieve errors] MERGE[Merge into cache] DELETE[Delete job] end SRC --> NORM --> DEDUP --> FILTER --> CHUNK --> QUEUE --> SUBMIT --> STATE STATE --> STATUS --> DONE DONE -->|no| STATUS DONE -->|yes| RESULTS RESULTS --> ERRORS --> MERGE --> DELETE ``` **Everything left of `CHUNK` is free and reduces the bill.** Everything right of it is orchestration. ## Preparation: where the savings are **Deduplicate.** Four million rows containing nine hundred thousand distinct addresses bills for four million. This is the largest single reduction available, it costs one `SELECT DISTINCT`, and it is skipped constantly. **Normalize first, then deduplicate.** `123 Main St` and `123 Main Street` are the same address. Deduplicating before normalization catches only exact string matches. See [Caching Geocoding Results](/architecture/caching-geocoding-results). **Filter what you already have.** `WHERE geocoded_at IS NULL`. On the second run this is most of the table. Run the preparation stage and count the rows that remain. Present that number before you present a timeline. It is usually a fifth of what was handed to you, and it changes the conversation. ## Job lifecycle, not bulk endpoint HERE's Batch API v7 is a job API. Six steps, and skipping any of them produces the failure modes below. 1. **Create** — returns `201` with a job ID 2. **Start** 3. **Poll status** until terminal 4. **Retrieve results** as a downloadable stream 5. **Retrieve errors** separately 6. **Delete** HERE documents [job status](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/job-status.html), [job lifecycle](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/job-lifecycle.html), and [limits and performance](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/limits-and-performance.html) separately. **Read the limits page before you design the pipeline** — concurrency limits determine your parallelism strategy, not the other way around. ## Status codes that are not errors These three produce more incident tickets than anything else in the pipeline. | Response | Meaning | Correct action | | ------------------------ | ------------------------------ | ------------------------------------------------------ | | `429` on job start | Concurrent job limit reached | Queue. Back off. Not a fault. | | `204` on errors endpoint | Zero errors in a completed job | Celebrate. Do not retry. | | `404` on results | Job has not succeeded yet | Poll status first. Results do not exist until success. | Code that treats any non-`200` as failure will log a perfectly successful job as broken, every night, until someone reads the specification. `204` means there were no errors. ## Idempotency and retries The pipeline must survive a worker restart without resubmitting. **Persist the job ID before you poll.** Not after. If the process dies between `start` and the first poll, you must be able to resume. The job is running on HERE's side whether or not your process remembers it. **Persist the chunk-to-job mapping.** Which rows are in which job. On resume, you retrieve results for jobs you already submitted and only submit chunks that have no job. **Retries are not resubmissions.** A failed poll is retried. A failed job is investigated. Resubmitting a job that already ran bills again. **Make chunking deterministic.** If chunk boundaries are derived from a stable ordering and a fixed size, a restarted process reconstructs the same chunks. If they are derived from a random shuffle, it does not. ```mermaid theme={null} stateDiagram-v2 [*] --> Pending: chunk created Pending --> Submitted: job_id persisted Submitted --> Running: started Running --> Running: poll (backoff) Running --> Succeeded Running --> Failed Succeeded --> Retrieved: results + errors fetched Retrieved --> Cleaned: job deleted Cleaned --> [*] Failed --> Pending: investigated, re-chunked note right of Submitted Crash here and resume: job_id exists, do not resubmit end note ``` ## Parallelization **Concurrency is bounded by contract, not by your worker count.** Exceeding the concurrent job limit returns `429` on start. Firing retries in a tight loop achieves nothing except log volume. Queue. **Chunk size is a trade-off.** Larger chunks mean fewer jobs, less orchestration overhead, and a larger blast radius when one fails. Smaller chunks mean more jobs competing for a fixed concurrency budget. Start with chunks large enough that job overhead is negligible relative to processing, and small enough that a single failure does not force you to reprocess a day's work. **Use gzip.** Compression is supported and materially reduces upload time and bandwidth on large inputs. **Do not parallelize by spawning more workers.** The bottleneck is a contract limit. More workers produce more `429`s. ## Handling partial success Some records will not resolve. This is normal and it is not an error condition for the job. **Read the errors endpoint.** It tells you which records failed and why. **Merge successes; queue failures.** A record that failed to geocode goes to an exception queue for review — human correction, source-system fix, or acceptance. It does not silently vanish, and it does not block the other 3.9 million. **Persist the confidence score.** A record that "succeeded" with a city-centroid fallback is a different outcome from a rooftop match. Threshold it. Sub-threshold records join the exception queue. See [Address Validation](/use-cases/address-validation). A pipeline that treats geocoding as binary — succeeded or failed — will persist low-confidence fallbacks as truth. That corruption is silent, permanent, and it propagates into every routing decision and every analytical aggregate downstream. ## Monitoring Nobody is watching a nightly job. Instrument it as if it will fail at 3am, because it will. **Job-level:** * Jobs submitted, running, succeeded, failed * Time in each state, with an alert on jobs running longer than expected * `429` rate on submission — sustained means your concurrency budget is misconfigured * Jobs not deleted after retrieval (they accumulate) **Record-level:** * Records submitted versus records returned * Error rate, and error *categories* * Confidence score distribution, not just the mean * Records entering the exception queue **Pipeline-level:** * Deduplication ratio — a sudden drop means the source changed * Cache hit rate on subsequent runs * Cost per thousand *distinct* addresses, not per thousand rows Alert on the confidence score distribution shifting, not just on errors. A map release, a source-system change, or a normalization regression shows up as a distribution shift long before it shows up as a failure. **Webhooks exist in beta.** HERE supports completion notifications with substitutable placeholders such as `${JOB_ID}` and `${JOB_STATUS}`. Attractive. If your pipeline's correctness depends on a webhook arriving, build the polling fallback anyway. ## Scaling considerations **Onboarding is bursty.** In a multi-tenant system, a large tenant arrives with millions of addresses. Batch concurrency limits are per-contract. **Queue onboarding jobs so they do not starve nightly enrichment for every other tenant.** Prioritize. **The second run is small.** After the initial backfill, `WHERE geocoded_at IS NULL` returns a trickle. Architect the pipeline for the backfill; it will spend its life doing very little. **Real-time and batch are different code paths.** Do not build a synchronous facade over the job API with a timeout. You will reproduce every problem async exists to prevent. **Result retrieval is a stream, not a payload.** Four million records do not fit in memory. Stream, parse, and write incrementally. ## Cost implications Ranked: 1. **Deduplicate.** The largest single reduction. Free. 2. **Filter already-geocoded records.** Free. 3. **Batch rather than real-time.** Cheaper per record. 4. **Do not resubmit jobs.** Persist state. 5. **Normalize before deduplicating.** Multiplies the dedup ratio. 6. **Never geocode that address again.** Write the result into a durable cache. See [Caching Geocoding Results](/architecture/caching-geocoding-results). The end state: every address has coordinates, a normalized form, a confidence score, and a timestamp. New addresses arrive at a trickle and are geocoded in real time. The batch job ran once, at migration, and then rarely. ## Alternative architectures **Stream through the real-time endpoint with a rate limiter.** Simpler code, no job lifecycle, no state machine. More expensive per record, and you now own the throttling. Defensible below a few hundred thousand records, where job overhead exceeds the saving. **Self-hosted geocoder for the backfill.** Nominatim over OSM extracts, or a national address file loaded into PostGIS. Zero marginal cost, coverage and quality are yours. For a bounded operating area with a large one-time backfill, this can be the correct trade — and you can use the paid API only for records the local geocoder cannot resolve confidently. A hybrid. **Address validation vendor instead of a geocoder.** If the goal is deliverability rather than coordinates, a postal-authority-backed validation service answers a question a geocoder cannot: does this address exist and does mail arrive there. They are not substitutes. See [Address Validation](/use-cases/address-validation). **Do not geocode.** For analytical workloads that aggregate to ZIP or county, a postal-code centroid join costs nothing and may be sufficient. Ask what resolution the downstream analysis actually needs before geocoding four million rooftops for a chart that buckets by state. ## Common mistakes **Treating the Batch API as a bulk endpoint.** It is a job lifecycle. **Not deduplicating.** Paying for repetition. **Not normalizing before deduplicating.** Catching only exact matches. **Losing the job ID on restart.** Full rebill. **Retrying `204` from the errors endpoint.** Zero errors. **Aggressively retrying `404` on results.** The job has not finished. **Hammering after `429` on start.** Concurrency limit. Queue. **Assuming all-or-nothing.** Read the errors endpoint. **Never deleting jobs.** They accumulate. **Depending on a beta webhook** without a polling fallback. **Loading four million results into memory.** **Persisting low-confidence matches as truth.** **Not writing results into a durable cache.** You will do this again next quarter, and pay again. **Onboarding jobs starving nightly enrichment** for concurrency. ## Production checklist * [ ] Input normalized, then deduplicated, then filtered on `geocoded_at IS NULL` * [ ] Distinct-address count reported before any job is submitted * [ ] Chunking deterministic and reproducible after restart * [ ] Job ID persisted **before** the first poll * [ ] Chunk-to-job mapping persisted * [ ] Polling uses exponential backoff * [ ] `429` on start queued, not retried tightly * [ ] `204` on errors understood as zero errors * [ ] `404` on results handled as "not yet" * [ ] Errors endpoint read; partial success handled * [ ] Confidence score persisted; sub-threshold records routed to an exception queue with an owner * [ ] Results streamed, not buffered * [ ] Jobs deleted after retrieval * [ ] Gzip enabled on large inputs * [ ] Polling fallback exists even if webhooks are used * [ ] Onboarding jobs prioritized separately from recurring enrichment * [ ] Monitoring: job states, `429` rate, confidence distribution, dedup ratio, cost per distinct address ## Related guides The job lifecycle in detail, and the status codes that are not errors. Where the output of this pipeline must land. Endpoint selection and confidence scores. Deduplication, batching, and precomputation as general patterns. ## Related use cases [Address Validation](/use-cases/address-validation) · [Location Intelligence](/use-cases/location-intelligence) · [Logistics Platform](/use-cases/logistics-platform) · [Reducing Google Maps Costs](/use-cases/reducing-google-maps-costs) ## HERE documentation * [Batch API v7 quick start](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/batch-api-quick-start.html) * [Job lifecycle](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/job-lifecycle.html) * [Job status](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/job-status.html) * [Limits and performance](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/limits-and-performance.html) *** Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Architecture Source: https://docs.placematic.com/architecture/index Design decisions that determine whether a location platform survives scale — and why the API call is almost never the thing that matters. # Architecture Nobody's location platform failed because they called the wrong endpoint. They failed because they called the right endpoint six hundred thousand times a day when one call would have done. Because a cache key omitted a tenant identifier. Because a routing constraint lived in a request builder instead of on a vehicle record, and a refactor dropped it. These pages are about those decisions. ## Why this section exists The guides tell you what an API does. The use cases tell you how to solve a business problem. Neither tells you why a system that works at ten vehicles collapses at two hundred, or why the migration that saved 60% on paper cost eighteen months of engineering. The pattern across every location system we have seen in production is the same: **the expensive mistakes are architectural, they are made in week one, and they are discovered in month six by operations.** A useful test. Take your highest-volume location call and ask: *what is the smallest unit of work that genuinely requires this?* If the answer is "an event," and you are calling it per packet, you have found the bug — and it is not in the code. ## The four decisions that matter Almost every location architecture question reduces to one of these. **1. What is the unit of work?** A GPS packet is not an event. A keystroke is not a query. An address is not an order. Systems bill on the unit they call, and the default unit is nearly always too small. The reverse-geocode-per-ping problem, the autocomplete-per-keystroke problem, and the routing-call-per-page-load problem are the same problem wearing three hats. **2. Is this computation or lookup?** A drive-time polygon is a computation. Testing whether a point is inside it is a lookup. The first belongs to a routing API, computed quarterly. The second belongs to your database, executed millions of times, for free. Teams that call an API for the second have built a spatial database with an invoice attached. **3. What is the latency budget, honestly?** Nothing is waiting for a nightly geocoding job. Nothing is waiting for a trip to be map-matched. Nothing is waiting for a territory to be re-optimized. Real-time endpoints exist because sometimes a user is watching a spinner. Paying for that latency when nobody is watching is the second-largest avoidable cost in this domain. **4. Where does the constraint live?** A truck's height is a property of the truck. A hazmat class is a property of the load. A tenant's vehicle profile is tenant data. Constraints that live in request builders get dropped in refactors, silently, and the response still returns `200`. ## What these pages assume You have read the [guides](/guides). You know what Matrix Routing does. You are not looking for parameter tables. You are deciding whether to build a queue, how to key a cache, what to shadow-write during a migration, and how to explain a bill to your CFO. ## The pages The loop that is costing you money, why it looks correct, and the decision tree. Normalization, invalidation, and the privacy question a permanent cache raises. Millions of addresses. Queues, retries, idempotency, and what to monitor. Gateway, queue, cache, failover. The reference design and where it bends. Dual-running, shadow comparison, and the rollback you must test before you need it. Isolation failure modes, quota allocation, and the cache key that leaks. A taxonomy of the patterns that move a bill by orders of magnitude. ## A principle worth stating plainly **Most location systems do not need most of a location platform.** Materialize what you can. Cache what does not move. Batch what nobody waits for. Compute in your own database what your own database can compute. What remains — routing a constrained vehicle, solving a VRP, matching a trace to a road network — is genuinely hard, genuinely worth buying, and genuinely small as a fraction of the calls most systems make. The architecture is what separates those two categories. ## Related API-level implementation guidance. Business problems and their reference architectures. ## HERE documentation * [HERE Technologies documentation](https://www.here.com/docs) *** Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Multi-Tenant Location Platform Source: https://docs.placematic.com/architecture/multi-tenant-location-platform Isolation failure modes, quota allocation under a shared contract, the cache key that leaks, and why your billing unit must correlate with your cost unit. # Multi-Tenant Location Platform From HERE's perspective there is one contract: yours. Every consequence of multi-tenancy follows from that sentence. Your tenants share a rate limit they cannot see, a concurrency budget they did not agree to, and an invoice they do not receive. ## The problem statement Four failure modes, all specific to multi-tenancy, none of which appear in a single-tenant system: **Noisy neighbour.** One tenant's bulk onboarding exhausts the shared rate limit. Another tenant's dispatcher gets `429` during morning assignment. **Cache leakage.** A geocode cache keyed on address, shared across tenants, is a channel. Tenant A's customer list is inferable from hit patterns and timing. **Cost attribution failure.** A tenant reverse-geocodes every GPS packet. You pay. They do not see it. You cannot price the feature because you cannot measure who consumed it. **Margin inversion.** You bill per shipment. You pay per API call. A customer with an inefficient integration destroys your margin on that account, silently, and the worse their engineering the worse your economics. None of these are routing problems. All of them will determine whether the platform is viable. ## The decision **Where does isolation live, and what is shared?** Specifically: which caches are keyed by tenant, where quota is enforced, and whether cost is attributable per tenant before the request leaves your infrastructure. ## Recommended architecture ```mermaid theme={null} flowchart TB subgraph T["Tenants"] A[Tenant A] B[Tenant B] C[Tenant C] end subgraph GW["Gateway — the only place this works"] AUTH[Authn / tenant resolution] QUOTA[Per-tenant quota
enforced before egress] BUDGET[Per-tenant budget
cost ceiling] PRIO[Priority classification
interactive > bulk] METER[Metering
per tenant, per endpoint] end subgraph Shared["Shared code, isolated data"] SVC[Route service] GC[(Geocode cache
key = tenant:hash addr)] MX[(Matrix cache
key = tenant:hash inputs)] ZONE[(Materialized zones
tenant-scoped)] end subgraph Q["Queue"] HOT[Interactive queue] BULK[Bulk queue] W[Workers] end subgraph H["HERE — one contract"] API[Routing / Matrix / Geocoding] end A --> AUTH B --> AUTH C --> AUTH AUTH --> QUOTA --> BUDGET --> PRIO --> METER METER --> SVC SVC --> GC SVC --> MX SVC --> ZONE PRIO --> HOT --> W PRIO --> BULK --> W GC -->|miss| W W --> API ``` **Shared code. Isolated data.** That is the whole principle, and every violation of it is a bug. ## The cache key that leaks This deserves its own section because the mistake is tempting and the reasoning is subtle. The optimization: a geocode is a pure function of an address. `123 Main Street, Chicago` resolves to the same coordinate for every tenant. Sharing the cache across tenants maximizes hit rate and minimizes spend. The problem: **cache hit patterns are a side channel.** A tenant can determine whether another tenant has geocoded a given address by measuring response latency. Repeated across a candidate list, that reconstructs a customer list. In some industries — healthcare logistics, legal courier, anything with a competitive customer base — that is a serious disclosure. A shared geocode cache in a multi-tenant system is a data leak. The savings were never worth it. Key every cache by tenant, and verify it with an explicit cross-tenant test in CI. **The clean resolution**, if hit rate genuinely matters: separate the *address-to-coordinate* mapping from the *tenant-to-address* mapping. The coordinate for `123 Main Street, Chicago` is a fact about the world. The fact that Tenant A has a customer there is tenant data. Store them separately. Share the first. Isolate the second. This gives you the hit rate without the channel, because a cache hit on the world-facts table reveals only that *someone* geocoded a public address — which is not a fact about any tenant. Timing still leaks whether the address was in cache at all. If your threat model includes a motivated adversarial tenant, key by tenant and accept the lower hit rate. Most platforms should. This same argument applies to matrix caches keyed on depot-stop sets, to materialized delivery zones, and to any derived spatial artifact. If the key reveals something about a tenant's business, the cache is a channel. ## Quota and the shared rate limit HERE sees one contract. Your tenants share its rate limit, and they do not know each other exists. **Enforce a per-tenant budget before the request leaves your infrastructure.** Not by observing `429`s and reacting — by refusing to send the request. Two ceilings, and they are different: **Rate ceiling** — requests per second per tenant. Prevents a runaway loop from consuming the shared limit. **Cost ceiling** — spend per tenant per month. Prevents a bad integration from consuming your margin. This is not a rate limit; a tenant can stay under the rate limit for thirty days and still be your entire invoice. **Priority is a queue property, not a request property.** A dispatcher's interactive request must never queue behind another tenant's bulk onboarding. Two queues. Interactive drains first. Batch API concurrency limits are per-contract. A large tenant arriving with millions of addresses will starve nightly enrichment for every other tenant unless onboarding jobs are queued separately and deprioritized. This is a real incident, and it happens on the day you close your biggest customer. **Reserve headroom.** Do not allocate 100% of your contracted rate limit across tenants. Leave a buffer for the tenant who onboards tomorrow, and for the incident where three tenants are busy at once. ## Cost attribution You cannot price a feature whose cost you cannot attribute. **Meter at the gateway**, per tenant, per endpoint, before egress. Not from HERE's invoice — that arrives monthly and is not broken down by your tenants. **Track calls per business event, per tenant.** Routes per dispatch. Geocodes per order. Reverse-geocodes per detected stop. The most useful multi-tenant metric is **HERE calls per business event, per tenant**. Total volume grows with the business; the ratio should not. A tenant whose ratio is ten times the median has an integration problem you can fix for them — and it is often a better conversation than an invoice. **Two responses to an inefficient tenant:** Fix it at the gateway. Cache aggressively, expose only call shapes you can afford, enforce debouncing server-side if the client will not. Or meter and pass through. Attribute the cost, price the feature accordingly. Most platforms do the first and find they never need the second. ## Your billing unit versus your cost unit If you bill per shipment and pay per API call, those units must be bounded relative to each other. A tenant who calls routing once per shipment and a tenant who calls it forty times per shipment produce the same revenue and forty times the cost. Your margin on the second account is negative, and nothing in your billing system tells you. **Either bound the calls per billable event**, structurally, at the gateway — or **bill on something correlated with calls.** **Evaluate asset-based pricing** if your tenants' vehicle populations are countable and their call volumes are not. A dispatcher rerouting two hundred trucks forty times a day is punished by call-volume pricing for operational diligence. See [HERE Pricing Explained](/getting-started/here-pricing-explained). Asset-based pricing availability depends on contract tier. Confirm it before it becomes load-bearing in your unit economics. ## Keys, and what tenants must never see **Your tenants never hold a HERE key.** One credential, held by your platform, never exposed, never proxied through in a way a customer's engineer could extract. **Translate HERE's error codes.** A `403` means your key lacks an entitlement. Your tenant has no HERE contract and cannot act on that. Surfacing it is confusing at best and information disclosure at worst. Map it to something meaningful in your domain. **Separate keys per environment.** Development, staging, production. Rotating a compromised development key must never cause a production outage. **Rotate with two keys live.** Issue the new key, deploy, verify traffic has moved, then revoke. Never revoke first. ## The redistribution question Exposing HERE APIs to your customers may constitute redistribution, which carries different licensing terms than internal use. **Resolve this before you build.** Discovering it during a customer's security review, or during your own contract renewal, is expensive. A spectrum, and where you sit determines your terms: * **Internal consumption** — your platform calls HERE, renders a map, shows an ETA. Your customer sees a feature. * **Pass-through** — your API returns HERE's response shape to your customer's engineers. * **Redistribution** — your customer builds against what is effectively a HERE proxy. The further right, the more likely you need explicit terms. This is exactly the conversation to have with a Gold Partner before you ship, not after. Placematic USA LLC is the contracting party for HERE services, which makes it a short conversation. ## Onboarding A new tenant arrives with a customer table. **Deduplicate before batch geocoding.** A raw export contains enormous repetition. Nine hundred thousand distinct addresses in four million rows. **Queue onboarding separately.** Deprioritized. Bounded concurrency. It must not compete with production traffic or with nightly enrichment for other tenants. **Materialize their spatial artifacts once.** Delivery zones, terminal catchments, territory polygons. Computed at onboarding, stored in PostGIS, queried locally forever after. **Set their quota and budget before they send a request.** A tenant with no ceiling is an incident waiting for a bug in their code. ## Scaling considerations **Cache hit rate falls when you key by tenant.** Accept it. The alternative is a channel. **Shared code, isolated data means one deployment.** Do not build per-tenant infrastructure to solve a data isolation problem. Key the data. **Reserve rate-limit headroom** proportional to your tenant growth rate, not your current tenant count. **The synchronous tier scales for free** if it serves from PostGIS and Redis. Materialized zones and cached geocodes do not care how many tenants you have. **Solve and matrix jobs partition naturally by tenant.** There is no cross-tenant optimization worth doing. ## Cost implications 1. **Per-tenant budgets** prevent one tenant's inefficiency from becoming your invoice. 2. **Deduplicate onboarding input.** The largest single reduction, and it is free. 3. **Materialize spatial artifacts per tenant, once.** Serviceability checks then cost nothing. 4. **Expose batch shapes, not single-item endpoints**, or your tenants will loop them. 5. **Meter per tenant, per endpoint.** You cannot optimize what you cannot attribute. 6. **Matrix instead of routing loops** — yours and theirs. ## Alternative architectures **Bring-your-own-key.** Each tenant holds a HERE contract. Clean commercially: their invoice, their rate limit, their redistribution question. Terrible for onboarding — every customer now runs a procurement process before they can use your feature. This kills product-led growth. Correct for enterprise-only products with long sales cycles. Wrong for everyone else. **Per-tenant infrastructure.** A separate database, separate cache, separate workers per tenant. Solves isolation by construction. Expensive, operationally heavy, and it solves a data-keying problem with hardware. Justified for regulated tenants with data residency requirements, not as a default. **Hybrid: shared platform, per-tenant key for large accounts.** Your enterprise tenants bring their own HERE contract; your long tail uses yours. Two code paths, two sets of bugs, and a redistribution question that varies per tenant. It works. Know that you are choosing it. **Self-host the routing engine.** Eliminates the redistribution question entirely and makes rate limits your own problem. You now maintain truck attributes, traffic, and map freshness for every tenant. Viable if location is your core competency. If you are a TMS vendor, this is a second product nobody is funded to own. **Build only what your customers cannot.** The strongest version of this architecture exposes *your* domain logic — territories, delivery zones, dispatch — computed on materialized spatial data in your own database, with HERE calls bounded and rare. Your customers never touch a routing API because they never need to. ## Common mistakes **A shared geocode cache across tenants.** A leak, not an optimization. **No per-tenant quota.** One tenant `429`s everyone. **No per-tenant cost ceiling.** Rate limits do not bound spend. **Reacting to `429` instead of preventing it.** **Onboarding jobs competing with production traffic.** **No cost attribution.** You cannot price the feature. **Billing unit uncorrelated with cost unit.** **Exposing single-item endpoints** and being surprised customers loop them. **Passing HERE error codes through unchanged.** **Building before answering the redistribution question.** **Allocating 100% of your rate limit across current tenants.** **Assuming tenants will integrate correctly.** Design the surface so misuse is bounded. **Per-tenant infrastructure to solve a cache-key problem.** **Vehicle constraints as service defaults** rather than tenant data. ## Production checklist * [ ] Redistribution and pass-through terms confirmed in writing before build * [ ] Every cache keyed by tenant; cross-tenant isolation asserted in CI * [ ] Per-tenant rate ceiling enforced at the gateway, before egress * [ ] Per-tenant cost ceiling enforced, separate from the rate ceiling * [ ] Two queues: interactive and bulk; interactive drains first * [ ] Onboarding jobs queued separately, deprioritized, bounded concurrency * [ ] Rate-limit headroom reserved for growth and for concurrent bursts * [ ] Metering per tenant, per endpoint, at the gateway * [ ] `HERE calls per business event, per tenant` dashboarded, with outlier alerting * [ ] Billing unit bounded relative to cost unit * [ ] Tenants never hold or see a HERE key * [ ] HERE error codes translated into domain-meaningful errors * [ ] Separate keys per environment; rotation with two keys live * [ ] Vehicle profiles and transport modes stored as tenant configuration * [ ] Batch-shaped endpoints exposed instead of single-item endpoints * [ ] Onboarding input deduplicated before batch geocoding * [ ] Spatial artifacts materialized per tenant at onboarding * [ ] Quota and budget set before a new tenant's first request ## Related guides Key handling, rotation without downtime, and error semantics. Normalization, invalidation, and the tenant-keyed cache. The tiers, the gateway, and the queue. Onboarding at millions of addresses without starving production. ## Related use cases [Embedding a Routing API](/use-cases/embedding-routing-api) · [Logistics Platform](/use-cases/logistics-platform) · [Fleet Routing](/use-cases/fleet-routing) ## HERE documentation * [Identity and Access Management](https://www.here.com/docs) * [Batch API v7 limits and performance](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/limits-and-performance.html) * [Matrix Routing API v8](https://www.here.com/docs/category/matrix-routing-api-v8) *** Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial systems. As a HERE Gold Partner, Placematic USA LLC is the contracting party — which makes the redistribution conversation a short one. [Talk to us](https://placematic.com/contact/). # Routing System Architecture Source: https://docs.placematic.com/architecture/routing-system-architecture Designing a production routing platform — the tiers that must not collapse into each other, where the queue belongs, what failover actually means, and how a constraint gets lost. # Routing System Architecture A routing platform is four systems wearing a trench coat. They have different latency budgets, different failure modes, different cost profiles, and different consumers. Collapsing them into one service is the most common structural mistake in this domain, and it is invisible until the day a dispatcher's page hangs on a ninety-second solve. ## The problem statement "We need routing" describes a dependency, not a system. What you actually need is: * **Planning** — solve a Vehicle Routing Problem. Seconds to minutes. Nobody is watching. * **Execution** — compute a leg. Milliseconds. Someone is watching. * **Reconstruction** — match a trace to road segments. Overnight. Nothing is waiting. * **Presentation** — render a map. Milliseconds. Static assets. Each has a different right answer for caching, retrying, queuing, and failing. A dispatcher clicking "optimize" and blocking on an HTTP request that takes ninety seconds is not a timeout bug. It is a tier collapse. No amount of tuning fixes it. ## The decision **Where do the boundaries go, and what crosses them?** Specifically: 1. What is synchronous, and what is a job? 2. What holds the constraint set? 3. What survives a HERE outage? 4. What can be answered without leaving your infrastructure? ## Recommended architecture ```mermaid theme={null} flowchart TB subgraph Clients DISP[Dispatcher UI] DRV[Driver app] API[Customer API] end subgraph Edge["Edge"] GW[API Gateway
authn, rate limit,
per-tenant budget] CDN[CDN
tiles] end subgraph Sync["Synchronous tier — ms"] ROUTE[Route service] L1[(Redis
hot cache)] L2[(PostgreSQL + PostGIS
geocode cache,
materialized zones,
lane distances)] end subgraph Async["Job tier — seconds to minutes"] Q[Job queue
priority: dispatch > bulk] W[Workers] JS[(Job state
ids persisted)] end subgraph Batch["Batch tier — overnight"] BW[Batch workers] MATCH[Trace matching] BGEO[Bulk geocoding] end subgraph HERE["HERE"] HR[Routing v8] HM[Matrix v8] HT[Tour Planning] HG[Geocoding & Search] HTILE[Map tiles] end subgraph Obs["Observability"] LOG[Structured logs
endpoint + status + tenant] MET[Metrics] ALERT[Alerts] end DISP --> GW DRV --> GW API --> GW DRV --> CDN --> HTILE GW --> ROUTE GW --> Q ROUTE --> L1 L1 -->|miss| L2 L2 -->|miss| HR ROUTE --> L2 Q --> W --> JS W --> HM W --> HT BW --> MATCH BW --> BGEO MATCH --> HR BGEO --> HG ROUTE --> LOG W --> LOG BW --> LOG LOG --> MET --> ALERT ``` Five things about this diagram matter more than the boxes. ## 1. The gateway is not optional It is where four things happen that cannot happen anywhere else: * **Authentication.** Your customers never see a HERE key. Ever. * **Rate limiting, per tenant.** HERE sees one contract. Without a per-tenant budget enforced before egress, your largest customer degrades service for everyone and you find out from a support ticket. * **Priority classification.** A dispatcher's interactive request must not queue behind a tenant's bulk onboarding. * **Cost attribution.** You cannot price a feature whose cost you cannot attribute. If you build one thing before the routing service, build the gateway. Retrofitting per-tenant quota onto a system that already has customers is a migration, not a feature. ## 2. The synchronous tier should rarely call HERE This is the architectural insight that separates viable platforms from expensive ones. Most questions asked at request time have answers already in your database: | Question | Where it should be answered | | ----------------------------------------- | -------------------------------------------------- | | Do we deliver to this address? | `ST_Contains` against a materialized isoline | | What is the lane distance for this quote? | A cached lane-distance table | | Where is this customer? | The geocode cache | | Which store is nearest? | `ST_DWithin` shortlist, then a small cached matrix | | What is this vehicle's last position? | Redis | **The synchronous tier's job is to serve from PostGIS and Redis.** A HERE call from the request path is a cache miss, and it should be rare enough to be worth logging as an event. If your checkout serviceability check calls an isoline API, you have built a spatial database with an API bill attached. Materialize the polygons. See [Delivery Zones](/use-cases/delivery-zones). ## 3. The queue is where solves live Matrix Routing at scale and Tour Planning are asynchronous by nature. Wrapping them in a synchronous facade with a thirty-second timeout reproduces every problem async was designed to prevent: timeouts, retries, duplicate submissions, double billing. **Submit, persist the job ID, return control, poll on a worker.** The job ID must be persisted *before* the first poll. If the process dies between submission and the first poll, the job is running on HERE's side regardless. Resubmitting a solved problem bills again and returns a different sequence, which confuses everyone downstream. **Follow the `statusUrl` HERE returns.** Do not construct it. **Priority is a queue property, not a request property.** Interactive dispatch ahead of bulk onboarding, always. ```mermaid theme={null} stateDiagram-v2 [*] --> Queued Queued --> Submitted: job_id persisted Submitted --> Polling Polling --> Polling: backoff Polling --> Succeeded Polling --> Timeout: solver did not converge Polling --> Failed Succeeded --> Retrieved Retrieved --> [*] Timeout --> Queued: raise maxTime, resubmit Failed --> Investigated note right of Timeout `timeout` is a tuning signal, not an error. Raise maxTime or reduce problem size. end note ``` ## 4. Constraints live on records, never at call sites A truck's height is a property of the truck. A hazmat class is a property of the load. A tenant's default transport mode is tenant configuration. A developer adding a new endpoint who copies a routing call without the vehicle profile has introduced a defect that is invisible in code review, returns `200`, and produces a route no truck can drive. There is no warning. There is no error. **Push the constraint set to the device, too.** The most dangerous pattern in mobile fleet software: server-side truck routing with constraints, device-side recalculation without them. The driver deviates. The phone reroutes. The constraints never left your backend. Vehicle profiles belong in the model layer, resolved once per request, applied uniformly. Validate them at write time — a vehicle record with a null height should fail validation before it reaches a routing call. ## 5. Retry semantics differ by status code This table is the most useful thing on this page. | Status | Meaning | Retry? | | ------------------------- | ------------------------------- | --------------------------------------------- | | `401` | Key missing, malformed, revoked | No — fix the credential | | `403` | Key valid, entitlement missing | **Never** — this is a commercial conversation | | `429` | Rate limit exceeded | Yes — exponential backoff with jitter | | `400` | Malformed request | No — fix the parameters | | `5xx` | Upstream failure | Yes — backoff, jitter, circuit breaker | | `200` with empty `routes` | No path exists | **No** — this may be the correct answer | `200` with `"routes": []` and a `notice` containing `routeCalculationFailed` is a valid response. Checking only `resp.ok` swallows route failures. For hazmat and truck routing, "no legal route" is a real and correct outcome — a system that retries with relaxed constraints has just produced an illegal route. **Retrying `403` is patient, well-engineered, and permanently futile.** It generates support tickets. Log the endpoint alongside the status code so `403` on tour planning and `429` on routing are distinguishable — one is a conversation with your account manager, the other with your architect. ## Failover: what it can and cannot mean Be precise about this, because "multi-provider failover" is usually a slide, not a system. **What genuinely fails over:** * **Tiles.** A CDN with a secondary origin. Users see a map. * **Geocoding.** A cached result, a secondary provider, or a stale coordinate with a warning. Degraded, not broken. * **Serviceability checks.** Materialized polygons in your own database. HERE being down is irrelevant. **What does not fail over:** * **Truck routing.** A second provider does not carry HERE's full constraint set — and since August 2026 this got harder to notice, not easier. A fallback to car routing produces an obviously wrong route. A fallback to a partial truck profile produces a plausible one. If legality depends on axle-group weight, tunnel category, or a cargo class the fallback cannot express, the route looks correct, passes review, and is illegal. **The second case is worse than the first.** * **Tour Planning.** A second solver produces a different sequence. Mid-day, that is chaos. * **Route Matching.** Your audit artifact must cite one map release. Two providers, two answers. Failing over truck routing to a provider with a narrower constraint set is the single most dangerous fallback in this domain — and a partially-constrained fallback is more dangerous than an unconstrained one, because it fails silently. Assert field-level parity in the facade, not merely that a provider responded. If the secondary cannot carry every constraint the primary was given, refuse to route. A dispatcher calling operations is a better outcome than a driver following a route through a low bridge. **Degrade honestly.** Serve the last known good route with a staleness indicator. Queue the request. Tell the user. Do not silently substitute a worse answer. **Circuit breakers must reset.** A breaker that opens on the first `429` and never closes turns a throttle into an outage. ## Observability Log the **endpoint, status code, tenant, and latency** for every external call. Not the body. **Metrics that matter:** * Cache hit rate, per cache — geocode, lane distance, matrix * HERE calls per business event (routes per dispatch, geocodes per order) * `429` rate by endpoint and by tenant * `403` count — should be zero in steady state; any occurrence is a licensing issue * Job queue depth and age, by priority class * Jobs running longer than expected * Ratio of reverse-geocode calls to detected stops Alert on **HERE calls per business event**, not on total call volume. Volume grows with the business; the ratio should not. A ratio that doubles overnight is a regression a deploy introduced, and it is visible hours before it is visible on an invoice. **Never log API keys.** Never put them in query strings — URLs are logged by proxies, load balancers, and CDNs whether you intended it or not. ## Scaling considerations **The synchronous tier scales with your traffic, for free**, provided it is serving from PostGIS and Redis. Indexed containment queries and cache lookups do not care about request volume. **The job tier scales with problem size, non-linearly.** Tour Planning solve time grows faster than stop count. Partition by geography before you partition by anything else — two independent depot regions are two problems, not one. **The batch tier scales with trace volume.** Two hundred vehicles at a ten-second ping rate is 648,000 points per day. Segment trips, drop idle periods, and downsample before submitting anything. Anything per-point multiplies by fleet size times ping rate. **HERE concurrency limits are per-contract, shared across your tenants.** Queue and prioritize at your layer. **`configuration.termination.maxTime` is a scaling parameter.** HERE's documentation example sets it to two seconds. On a two-hundred-stop problem that produces a legal solution a dispatcher will reject. Find the knee empirically, per problem size. ## Cost implications The architecture *is* the cost model. **Requests answered from PostGIS cost nothing.** Materialize zones, cache lane distances, precompute matrices for stable input sets. **Matrix instead of routing loops.** A 20×500 assignment problem is 10,000 routing calls or one matrix. That is a complexity difference, not a rate difference. See [Routing vs Matrix](/architecture/choosing-routing-vs-matrix). **Hash the problem.** If the stop set, fleet, and constraints are unchanged, reuse the solution rather than re-solving. **Nothing expensive touches a GPS packet.** Detect stops locally. Geocode events. Match trips overnight. **`return` explicitly on every routing call.** Nothing consumes turn-by-turn `instructions` server-side. **Cache route geometry and ETAs with different TTLs.** The path between fixed points is stable for hours. The ETA is not. ## Alternative architectures **Monolith with a routing module.** Perfectly reasonable below a certain scale. The tiers still exist — they are just packages rather than services. Keep the boundaries even if you do not distribute them. The failure mode of a monolith is not the monolith; it is a dispatcher's request blocking on a solve because nothing forced you to think about it. **Serverless job workers.** Attractive for the batch tier, which is bursty and idle most of the day. Watch two things: cold starts against polling backoff, and function timeouts against solve durations. Persist job state externally regardless. **Event-driven throughout.** GPS ingest, stop detection, and trip closure are naturally events. A log-based architecture — Kafka or equivalent — suits the batch tier well and the synchronous tier badly. Do not force it into the request path. **No queue.** If your solves are small and your tenants few, a synchronous matrix call may be genuinely sufficient. Be honest about whether you are below that threshold, and note that the threshold moves as you grow while the code does not. **Bring your own solver.** If your optimization objective is genuinely custom, feed OR-Tools with a cached matrix. The matrix is the interface. This is a legitimate build; approximating a matrix with routing loops to feed it is not. ## Common mistakes **Tier collapse.** A synchronous request blocking on a solve. **No gateway.** No per-tenant budget, no cost attribution, no priority. **Calling HERE from the request path** for questions PostGIS answers. **Constraints at call sites** rather than on records. **Constraints on the server, absent on the device.** **Retrying `403`.** **Treating `200` as success without checking `routes.length`.** **Failing over truck routing to a provider with a narrower constraint set.** **Circuit breakers that never reset.** **Blocking on an async job.** **Losing the job ID on restart.** **Constructing the `statusUrl`.** **Logging keys, or putting them in query strings.** **Alerting on total call volume** rather than calls per business event. **One key across environments.** A leaked development key now requires a production rotation. ## Production checklist * [ ] Gateway enforces authentication, per-tenant rate limit, and per-tenant budget before egress * [ ] Priority classes: interactive dispatch ahead of bulk operations * [ ] Synchronous tier serves serviceability, lane distances, and geocodes from PostGIS/Redis * [ ] HERE calls from the request path logged as cache-miss events, and rare * [ ] Solves and large matrices submitted as jobs; job ID persisted before the first poll * [ ] `statusUrl` followed verbatim * [ ] Vehicle profiles and constraint sets stored as records, validated at write time * [ ] Constraint set pushed to the driver device; recalculation path tested * [ ] Retry policy differentiated by status code; `403` never retried * [ ] `200` with empty `routes` handled as a legitimate outcome * [ ] No failover from constrained truck routing to a provider that cannot carry every constraint; parity asserted per field, not per response * [ ] Circuit breakers reset * [ ] CDN in front of tiles; browser keys domain-restricted * [ ] Structured logs: endpoint, status, tenant, latency — never keys, never bodies * [ ] Alerts on HERE calls per business event, `403` count, `429` rate, job queue age * [ ] Separate keys per environment * [ ] Trap-geometry assertions in CI for truck routing, with a `car`-mode control ## Related guides Transport modes, `return` fields, and the `200` that is not success. Key handling, rotation without downtime, and error semantics. Async lifecycle, `statusUrl`, and the flat result array. Solve as a job, `maxTime` as a scaling parameter. ## Related use cases [Fleet Routing](/use-cases/fleet-routing) · [Logistics Platform](/use-cases/logistics-platform) · [Embedding a Routing API](/use-cases/embedding-routing-api) · [Vehicle Tracking](/use-cases/vehicle-tracking) ## HERE documentation * [Routing API v8](https://www.here.com/docs/category/routing-api-v8) * [Matrix Routing API v8](https://www.here.com/docs/category/matrix-routing-api-v8) * [Tour Planning API](https://docs.here.com/tour-planning/docs/introduction-tour-planning) *** Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # HERE Geocoding vs Google Maps Geocoding Source: https://docs.placematic.com/comparisons/here-geocoding-vs-google-maps Address precision, per-field confidence scoring, batch architecture, and why 'more accurate' cannot be answered without your own address dataset. # HERE Geocoding vs Google Maps Geocoding **Choose HERE when your workload is address resolution at volume**, when you need per-field confidence rather than a single match level, or when batch geocoding is a meaningful fraction of your bill. **Choose Google when your product discovers places** — businesses, hours, reviews, categories. Its place database is better. Not marginally. Categorically. Most systems need both, and the right architecture says so out loud. ## Comparison scope Geocoding, reverse geocoding, autocomplete, and place discovery. Not routing (see [HERE Routing vs Google Maps](/comparisons/here-routing-vs-google-maps)), not rendering, not platform-level ecosystem fit (see [HERE vs Google Maps](/comparisons/here-vs-google-maps)). HERE's capability is delivered through **Geocoding & Search v7**, which consolidated the former Geocoder API 6.2 and Places/Search APIs. Those legacy APIs are in maintenance and should not be used for new work. HERE Geocoding & Search v7 uses **per-endpoint hosts**, not one base URL: | Endpoint | Host | | ------------------ | --------------------------------- | | `/v1/geocode` | `geocode.search.hereapi.com` | | `/v1/revgeocode` | `revgeocode.search.hereapi.com` | | `/v1/autocomplete` | `autocomplete.search.hereapi.com` | | `/v1/autosuggest` | `autosuggest.search.hereapi.com` | | `/v1/discover` | `discover.search.hereapi.com` | This surprises teams building a single client. Verified July 2026 against [HERE Geocoding & Search documentation](https://docs.here.com/geocoding-and-search/docs/geocode). ## Decision summary | Requirement | Better fit | Why | | ---------------------------------------- | ---------- | ---------------------------------------------------------------------- | | Street address → coordinates | Comparable | Both are strong. Test on your addresses | | Per-field match confidence | HERE | `fieldScore` breaks down confidence by address component | | Distinguishing rooftop from interpolated | HERE | `houseNumberType: PA` vs `interpolated` is explicit | | Structured query input | HERE | `qq` accepts typed fields; avoids free-text ambiguity | | Business POI discovery | Google | Place database, hours, reviews, photos | | Consumer autocomplete | Google | Typo tolerance and ranking from enormous query volume | | Batch geocoding at millions of records | HERE | Batch API v7 is a job API with a documented lifecycle | | Address deliverability validation | Neither | Use a postal-authority validation service | | Routing-aware coordinates | HERE | `access` returns the road-network entry point separate from `position` | | Cost at production volume | Depends | On mix, tier, batching, caching. Not on rate card | ## Where HERE is stronger ### Per-field confidence scoring This is the difference most evaluations miss, and it is the one that matters for data quality. HERE returns a `scoring` object on every geocode result: ```json theme={null} "scoring": { "queryScore": 0.97, "fieldScore": { "country": 1, "city": 1, "streets": [1], "houseNumber": 1, "postalCode": 0.82 } } ``` *Response abbreviated. Source: [HERE geocode documentation](https://docs.here.com/geocoding-and-search/docs/geocode).* `queryScore` is overall match quality. `fieldScore` tells you *which component* was uncertain. A result with `queryScore: 0.95` and `fieldScore.houseNumber: 0.4` matched the street confidently and the house number poorly. That is a different operational risk than `fieldScore.postalCode: 0.8` — one produces a failed delivery, the other does not. A single aggregate confidence number cannot express this. Store `fieldScore`. Google returns `location_type` (`ROOFTOP`, `RANGE_INTERPOLATED`, `GEOMETRIC_CENTER`, `APPROXIMATE`) and `partial_match`. Useful, coarser. ### Rooftop versus interpolated is explicit HERE returns `houseNumberType`, with `PA` indicating a point address and `interpolated` indicating a position estimated along a street segment. An interpolated address may be tens of metres from the actual building. For last-mile delivery that is the difference between the right door and the neighbour's. **Persist this field.** A system that stores coordinates without it cannot distinguish a surveyed rooftop from an arithmetic estimate. ### `access` versus `position` HERE returns two coordinates per result: * `position` — the location of the address itself * `access` — the point on the road network where a vehicle would arrive For routing to an address, `access` is the correct target. Routing to `position` may route to the geometric centre of a building, or to a point separated from the road by a car park. Google's geocoding response does not distinguish these. This is a small, specific, real advantage for fleet and delivery systems. ### Structured queries (`qq`) HERE accepts either free-text (`q`) or a structured query (`qq`) with typed fields: qq=street=rue de bale;city=Strasbourg;postalCode=67100;country=FRA For validating user-entered addresses, structured input outperforms free text plus a `types` filter. Do not send both `q` and `qq` in the same request. HERE's own developer guidance notes that mixing them produces inconsistent `queryScore` values. Pick one. ### Batch geocoding architecture HERE's Batch API v7 is a **job API**: create → start → poll status → retrieve results → retrieve errors → delete. It is asynchronous by design, cheaper per record, and built for millions of rows. Three response codes routinely misread as failures: | Response | Meaning | | ------------------------ | -------------------------------- | | `429` on job start | Concurrency limit reached. Queue | | `204` on errors endpoint | Zero errors. Not a failure | | `404` on results | Job has not succeeded yet | See [High-Volume Geocoding](/architecture/high-volume-geocoding). ## Where Google is stronger ### Place and business data **For business names, opening hours, reviews, photos, and category coverage, Google's data is categorically better than HERE's.** Placematic sells HERE. We would rather you know this before a migration than after. If your product answers "what's a good restaurant near me," that surface belongs on Google. HERE Geocoding & Search v7 substantially improved POI coverage over its predecessors, and it still is not competitive with Google's business database. ### Consumer autocomplete Google's ranking, aliasing, and typo tolerance reflect consumer query volume HERE does not have. For a consumer search box, this is decisive. For a **street address field** — checkout, dispatch, delivery — HERE's `/autocomplete` is purpose-built and competitive. `/autocomplete` and `/autosuggest` are not synonyms and this is a common integration error. * **`/autocomplete`** — completes structured address input. Forms, checkout, delivery. * **`/autosuggest`** — broader discovery. Returns addresses, POIs, and categories. Handles misspellings. * **`/discover`** — actual place results for a query. This is the endpoint for "find restaurants," not `/geocode`. Source: [HERE developer best practices](https://www.here.com/learn/blog/here-geocoding-and-search-api-developer-insights-and-best-practices). ### Ecosystem More engineers have integrated Google's geocoder. This is a real cost in your engineering budget. ### Low volume Below roughly ten thousand geocodes per month, both are effectively free. Migration engineering exceeds the saving. ## Where the difference is commercial or operational ### Caching and storage terms **Read your contract's caching and storage terms before designing a permanent geocode cache.** Both vendors place restrictions on retaining geocoded coordinates, and those restrictions differ by platform, by plan, and over time. We will not summarize them here because they change and because a summary that is wrong is worse than no summary. Verify with your account manager, and get the answer in writing. This matters architecturally. A geocode cache is the single largest cost lever available — see [Caching Geocoding Results](/architecture/caching-geocoding-results) — and its permissibility is a contract term, not an engineering decision. ### Pricing We will not publish a savings percentage. Cost depends on API mix, monthly volume, region, contract terms, billing SKU, batching, and caching. These span more than an order of magnitude. Structurally: * Both bill per request for real-time geocoding * HERE's Batch API prices differently from real-time; latency-tolerant work belongs there * Google bills autocomplete on a session-token model where sessions are used; HERE bills per request. **This means the meters do not translate.** A keystroke-level comparison and a session-level comparison are different measurements. * Free bundles are per-API and do not pool Current rates: [Google Maps Platform pricing](https://developers.google.com/maps/billing-and-pricing/pricing) · [Placematic pricing calculator](https://placematic.com/here-location-services/here-pricing/). ## Why "more accurate" is not a number Any vendor claiming a universal geocoding accuracy percentage is describing a benchmark on a dataset that is not yours. Geocoding accuracy varies by: * **Country and region.** A platform strong in Germany may be weak in Indonesia. * **Urban versus rural.** Rooftop match rates diverge sharply. * **Address type.** Apartments, PO boxes, rural routes, new-build estates. * **Input quality.** Your own data's normalization determines much of the result. * **What "accurate" means.** Match rate? Positional error? Rooftop fraction? These optimize differently. A geocoder that resolves 97% of US urban addresses to rooftop may resolve 60% of your rural service-area addresses to a street centroid. Both statements can be true of the same platform. **Neither we nor any vendor can tell you which is more accurate for your data. We can tell you how to find out.** ## How to evaluate with your own data Run this before you decide. It takes a day. **Assemble a representative set.** 1,000 addresses minimum, drawn from your actual data — not a curated list. Deliberately include: * Urban and rural, in your operating regions * Apartments, units, and suites * Rural routes and non-standard formats * New-build addresses (under two years old) * Addresses that have previously failed delivery * Deliberately misspelled and truncated variants * Every country you operate in, weighted by volume **Establish ground truth.** For a subset — 100 is enough — obtain verified coordinates. Site surveys, driver-confirmed delivery GPS, or parcel-level data from a local authority. **Measure four things separately:** | Metric | Definition | | ---------------------- | ------------------------------------------------------------------------------------------ | | Match rate | Fraction returning any result | | Rooftop fraction | HERE: `houseNumberType: PA`. Google: `location_type: ROOFTOP` | | Positional error | Distance from ground truth, reported as a **distribution** — median, p90, p99 — not a mean | | Confidence calibration | Among results the platform scored highly, how many were actually correct? | **Confidence calibration is the metric almost nobody measures and the one that determines data quality.** A geocoder with a lower match rate but well-calibrated confidence is *more useful* than one with a higher match rate and confidence scores that do not predict correctness. The first lets you route uncertain records to an exception queue. The second silently corrupts your database. Plot correctness against `queryScore` (HERE) and against `location_type` (Google). A well-calibrated geocoder produces a monotonic curve. **Test the batch path separately.** Real-time and batch may return different results. If your production workload is batch, benchmark batch. **Forecast cost from actual call counts**, pulled from logs. Not estimates. Apply caching and deduplication to *both* platforms before comparing — otherwise you are comparing your inefficiency on two rate cards. ## Architecture implications **Caching is platform-independent and dominates the bill.** Normalize, deduplicate, cache permanently, invalidate on events rather than on a TTL. This halves most geocoding bills before any vendor decision. See [Caching Geocoding Results](/architecture/caching-geocoding-results). **Store the confidence structure, not just coordinates.** Storing a coordinate without its confidence score converts a probabilistic estimate into a fact. Every downstream consumer treats it as truth. A low-confidence fallback silently corrupts revenue maps, routes vehicles to centroids, and produces failed deliveries. Persist: coordinates, `access` point, normalized address, `queryScore`, `fieldScore`, `houseNumberType`, `resultType`, `geocoded_at`. **Autocomplete is a debouncing problem, not a vendor problem.** Undebounced, it bills once per character typed. 200–300ms. **Per-endpoint hosts complicate a single HTTP client.** Minor, but it affects connection pooling and any allowlist your security team maintains. ## Migration mapping | Google | HERE | Notes | | --------------------------- | ------------------------------------------------------------ | ------------------------------------------------ | | Geocoding API | `GET geocode.search.hereapi.com/v1/geocode` | `q` for free text, `qq` for structured. Not both | | Reverse Geocoding API | `GET revgeocode.search.hereapi.com/v1/revgeocode` | `types=area` filters to admin areas | | Places Autocomplete | `/v1/autocomplete` (addresses) or `/v1/autosuggest` (places) | Two endpoints, not one | | Places Text Search / Nearby | `/v1/discover` | Not `/geocode` | | Place Details | `/v1/lookup` by HERE ID | Store the ID | | — | `POST /v1/multi-revgeocode` | Batch reverse geocoding in one request | | `location_type` | `houseNumberType` + `resultType` | Different taxonomies. Do not map naively | | `partial_match` | `queryScore` + `fieldScore` | Richer. Rebuild your quality logic | **Semantic differences:** **Coordinate order.** HERE returns `{"lat": ..., "lng": ...}`. GeoJSON is `[lng, lat]`. **Two coordinates per result.** `position` and `access`. Decide which your system means. **No universal confidence threshold.** A `queryScore` of 0.9 does not mean what a Google `partial_match: false` means. Recalibrate your thresholds against your own ground truth before cutover. Do not port the number. **Batch is a job lifecycle.** Not a bulk endpoint. See [Google Migration Architecture](/architecture/google-migration-architecture). ## Cost model **What creates billable activity:** * One call per real-time geocode or reverse geocode * One call per autocomplete request — which is per keystroke unless debounced * Batch records, priced separately from real-time * Reverse-geocoding every GPS packet, which is 648,000 calls a day for 200 vehicles on a 10-second interval **Request multiplication risks:** Undebounced autocomplete. Reverse geocoding on packet ingest rather than on stop detection. Re-geocoding a stable customer address on every order. Looping single reverse-geocodes instead of `POST /multi-revgeocode`. **Batching opportunities:** anything written to a database rather than rendered to a screen. **Caching implications:** the single largest lever, subject to your contract's storage terms. ## Common decision mistakes **Comparing per-1,000 rates.** Close at entry level. Meaningless. **Accepting a universal accuracy figure.** Including ours. **Comparing mean positional error.** Report the distribution. p99 is where failed deliveries live. **Not measuring confidence calibration.** **Migrating a consumer place-search feature to HERE for cost reasons.** The feature regresses. Users leave. **Using `/geocode` for place search.** That is `/discover`. **Confusing `/autocomplete` with `/autosuggest`.** **Sending `q` and `qq` together.** **Discarding `fieldScore` and `houseNumberType`.** **Routing to `position` instead of `access`.** **Porting confidence thresholds across platforms.** **Designing a permanent cache without reading the contract's storage terms.** **Migrating before caching and deduplicating.** You may be migrating waste. ## Choose HERE when * Address resolution at volume is your workload * You need per-field confidence to drive an exception queue * Rooftop versus interpolated must be distinguishable * You route to addresses and need the road-network access point * Batch geocoding is a meaningful fraction of your bill * You already license HERE for routing and can consolidate ## Choose Google when * Your product discovers businesses, and hours, reviews, or categories matter * Consumer autocomplete quality is a product differentiator * Your volume is low enough that migration engineering exceeds savings * Ecosystem familiarity and hiring are material constraints ## The architecture most teams land on HERE for address resolution, batch geocoding, and routing. Google for consumer place discovery and business search. Two vendors, each doing what it is good at. **Document that as a decision** before someone frames it as a failed migration. ## Related documentation Endpoint selection, confidence scores, and the caching that halves the bill. Why geocoding and deliverability validation are different products. Normalization, invalidation, and the privacy question. Millions of addresses, queues, idempotency, and what to monitor. Also: [Batch Geocoding](/guides/batch-geocoding) · [Reverse Geocoding](/guides/reverse-geocoding) · [Points of Interest](/guides/points-of-interest) · [HERE vs Google Maps](/comparisons/here-vs-google-maps) ## Sources **HERE** * [Geocoding & Search v7 introduction](https://docs.here.com/geocoding-and-search/docs/introduction-to-here-geocoding-search-api-v7) * [Geocode endpoint](https://docs.here.com/geocoding-and-search/docs/geocode) — `scoring`, `fieldScore`, `houseNumberType`, `access` * [Spatial references](https://docs.here.com/geocoding-and-search/docs/code-geocode-spatial-reference) * [Developer insights and best practices](https://www.here.com/learn/blog/here-geocoding-and-search-api-developer-insights-and-best-practices) * [Batch API v7](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/batch-api-quick-start.html) **Google** * [Geocoding API](https://developers.google.com/maps/documentation/geocoding) * [Places API](https://developers.google.com/maps/documentation/places/web-service) * [Maps Platform pricing](https://developers.google.com/maps/billing-and-pricing/pricing) **Placematic** * [Commercial comparison and pricing overview](https://placematic.com/compare/here-geocoding-vs-google-maps/) *Verified July 2026. Endpoint hosts, response fields and pricing change; verify against primary sources.* *** Need to compare these platforms with your own request mix? Placematic can help you run a technical and cost evaluation using representative routes, addresses and production volumes. Placematic is an official HERE Technologies reseller and implementation partner. [Cost Reduction Audit](https://placematic.com/here-location-services/cost-reduction-audit/). # HERE Routing vs Google Maps Routing Source: https://docs.placematic.com/comparisons/here-routing-vs-google-maps Google Large Vehicle Routing versus HERE truck routing — constraint expressiveness, per-axle-group weight, toll cost, coverage limits, and an honest account of where Google Routes is the better engine. # HERE Routing vs Google Maps Routing **Choose HERE when a vehicle's physical or legal characteristics must constrain the route beyond dimensions and gross weight.** Per-axle-group weight, the full ADR hazardous cargo set, toll cost for the described vehicle, and coverage outside the contiguous United States. **Choose Google when you are moving passenger vehicles or people**, or when your commercial vehicle operation is US-domestic, always online, and does not need toll cost before dispatch. Everything below is elaboration on those two sentences. **This page was substantially wrong before August 2026 and has been corrected.** Earlier revisions stated that Google Maps Platform had no commercial vehicle routing profile. On 17 August 2026 Google made Large Vehicle Routing generally available, and that statement stopped being true. If you are reading a comparison of these platforms written before that date — including our own — it is wrong on this point. ## Comparison scope This page compares **routing engines**: path computation, travel-time estimation, matrices, and vehicle constraint handling. It does not compare geocoding (see [HERE Geocoding vs Google Maps](/comparisons/here-geocoding-vs-google-maps)), map rendering, or platform-level ecosystem fit (see [HERE vs Google Maps](/comparisons/here-vs-google-maps)). Google's routing capability is delivered through the Routes API, which supersedes the legacy Directions and Distance Matrix APIs. Where this page says "Google," it means Routes API unless stated otherwise. Google deprecated the Directions API and Distance Matrix API in favour of the Routes API, with a documented sunset timeline. If your evaluation is based on Directions API documentation, you are evaluating a product Google is retiring. Verify current status at [Google Maps Platform documentation](https://developers.google.com/maps/documentation/routes). ## Google Large Vehicle Routing: what it covers Google Maps Platform made Large Vehicle Routing generally available on 17 August 2026, delivered through the Routes API, the Route Optimization API, and the Navigation SDK by route token. **Expressible.** Total height, width, length, gross weight, axle count, and hazardous goods classification. The request requires `travelMode=TRUCK` with a `vehicleInfo` object inside `routeModifiers`, and `routingPreference=TRAFFIC_AWARE_OPTIMAL`. **Documented as unsupported.** Truck toll prices. Speed limits. Routing for radioactive hazardous materials. **Coverage.** The 48 contiguous United States only. No Alaska, Hawaii, Canada, or Mexico. **Access.** Documented as generally available, but provisioned on request rather than enabled by default on every project. **Compliance posture.** The response carries a flag indicating that route restrictions were partially ignored, and the documentation states that drivers must not rely solely on the returned route to be safe or legal. Verified August 2026 against Google's [large vehicle routing overview](https://developers.google.com/maps/documentation/routes/lvr). Capabilities change. Check the current reference before architecting, and confirm your project is provisioned before planning a sprint around it. ## Decision summary | Requirement | Better fit | Why | | ------------------------------------------------------- | ------------------- | ------------------------------------------------------------------------------------------- | | Truck routing, US domestic, dimensions and gross weight | Comparable | Google Large Vehicle Routing expresses these since August 2026 | | Truck routing outside the contiguous United States | HERE | Google LVR covers the 48 contiguous states only | | Per-axle-group weight expression | HERE | `weightPerAxleGroup` with single, tandem, triple, quad, quint. Verify Google's equivalent | | Toll cost for a described commercial vehicle | HERE | Google documents truck toll prices as unsupported | | Posted speed limits returned with the route | HERE | Google documents speed limits as unsupported for LVR | | Radioactive hazardous cargo | HERE | Google documents radioactive materials routing as unsupported | | ADR tunnel category | HERE | `tunnelCategory` B/C/D/E is a native parameter | | Passenger car navigation | Comparable | Both produce excellent car routes; test on your corridors | | Consumer ride-hailing / delivery of people | Google | Ecosystem, traffic density, and familiarity | | Large origin-destination matrices | HERE | Asynchronous Region and Profile modes reach far larger sizes — *with conditions, see below* | | Multi-stop optimization with constraints | HERE | Tour Planning is a VRP solver, included in the HERE Base Plan | | GPS trace to road segments | HERE | Route Matching. Google has no equivalent product | | Hours of service coupling | Neither | Both require an external HOS layer | | EV routing with charge state | Both, differently | HERE exposes consumption models and charging as routing outputs; LVR does not cover EV | | Ecosystem, hiring, Stack Overflow depth | Google | Materially larger developer population | | Contractual price stability | Depends on contract | Neither is inherently stable; this is a commercial term | ## Where HERE is stronger ### The tail of the commercial vehicle constraint set The gap is no longer about whether a truck profile exists. It is about how much of your vehicle the profile can describe. HERE Routing v8 accepts, as first-class inputs, a vehicle description that includes: * Physical dimensions — `height`, `width`, `length`, `trailerLength` (all in centimetres) * Weight — `grossWeight`, `currentWeight`, `emptyWeight`, `payloadCapacity` (kilograms) * Axle configuration — `axleCount`, `weightPerAxle`, and `weightPerAxleGroup` with `single`, `tandem`, `triple`, `quad`, `quint` groups * `tunnelCategory` — the ADR cargo tunnel restriction code, values `B`, `C`, `D`, `E` * `shippedHazardousGoods` — a list drawn from `explosive`, `gas`, `flammable`, `combustible`, `organic`, `poison`, `radioactive`, `corrosive`, `poisonousInhalation`, `harmfulToWater`, `other` * `kpraLength` — kingpin-to-rear-axle, relevant to California and Idaho restrictions * `category: lightTruck` — a legal exemption that does not waive physical dimension limits * `trailerCount`, `tiresCount`, `licensePlate` for environmental-zone evaluation Verified against the [HERE Matrix Routing v8 OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi), version 8.47.0, retrieved July 2026. The `truck` object is marked deprecated in favour of `vehicle`; both currently accept the same fields. Check the current specification before implementing. Google's documented vehicle parameters cover total dimensions, gross weight, axle count, and hazardous goods classes. **Verify directly against Google's large vehicle routing reference whether per-axle-group weight can be expressed at all.** We will not assert its absence from field names alone. **The gap is jurisdictional and cargo-specific, not absolute.** A 4.1 m trailer under a 3.4 m clearance in Ohio is handled by both platforms. The same trailer entering Ontario is handled by one. A five-axle tractor-trailer whose legality is set by the US federal bridge formula — written per axle *group*, not per axle — is handled by whichever platform can express the group. Two questions settle the platform choice before any benchmark: **does your operation leave the contiguous 48, and does your jurisdiction regulate by axle group.** Either answer decides it on its own. ### Toll cost for the described vehicle HERE returns expected toll cost computed against the vehicle you described — class, axle count, dimensions, weight. Google documents truck toll prices as unsupported for large vehicle routing. This is not a reporting feature. On a single lane, the toll figure differs materially depending on whether the vehicle profile was supplied — a passenger-class estimate and a commercial one are different numbers for the same road. If your dispatcher prices a load before the truck moves, the toll figure is the quote. See [Toll Cost](/guides/toll-cost). ### Route Matching HERE offers map matching: taking a noisy GPS trace and returning the road segments actually travelled, with their attributes. This is what IFTA jurisdiction-mile reporting and defensible speed compliance require. Google Maps Platform has no equivalent product. Nearest-address reverse geocoding does not substitute — it returns an address, not a segment, and it will not survive an audit. See [Route Matching](/guides/route-matching) and [ELD Platform](/use-cases/eld-platform). ### Tour Planning HERE Tour Planning is a Vehicle Routing Problem solver: capacitated VRP, time windows, multi-depot, heterogeneous fleets, pickup-and-delivery, priorities, and reloads. HERE documents Tour Planning as included in the HERE Base Plan. Confirm your own entitlement; Base Plan inclusion and your contract's inclusion are separate facts. Source: [HERE Tour Planning introduction](https://docs.here.com/tour-planning/docs/introduction-tour-planning). Google's Route Optimization API now accepts large vehicle profiles, which narrows this. Compare the two on your own problem shape rather than on product names: the question is whether the solver assigns jobs across a fleet under capacity, time windows, and skill constraints, and how it reports the jobs it could not assign. ### Coverage outside the contiguous United States HERE carries commercial road attributes in more than 200 countries. Google Large Vehicle Routing stops at the contiguous 48. For a carrier running US–Canada or US–Mexico lanes, this is not a feature comparison. It is a coverage boundary that ends the evaluation. ## Where Google is stronger ### Passenger vehicle traffic and ETA Google's traffic data derives from an enormous consumer device population. For dense urban passenger-car routing, this is a real advantage and it is honest to say so. Large Vehicle Routing extends this with truck-observed speed data in the US, which is a genuine improvement over applying car speeds to a truck profile. **Whether it is an advantage on your corridors, at your departure times, is an empirical question.** We give a test plan below. Do not accept either vendor's claim, including ours. ### Ecosystem More engineers have shipped Google Maps integrations. More Stack Overflow answers exist. More libraries assume it. Hiring is easier. This is a real cost that appears in your engineering budget, not on a rate card. ### Driver-facing familiarity If your drivers already navigate with Google Maps, the adoption cost of a Google-rendered in-cab experience is close to zero. Driver adoption is a real project risk and it does not appear in any API comparison. ### Consumer-facing everything If your product is consumer navigation, ride-hailing, or anything where users recognize the map's appearance and expect Street View, Google is the default and the default is correct. ### Simplicity at low volume Below roughly ten thousand routing calls per month, both platforms are effectively free and Google's onboarding is faster. Migration engineering cost dominates any saving. Do something else. ## Where the difference is commercial, not technical ### Matrix size limits Matrix ceilings are frequently quoted as a single number. There isn't one. The limits, from the [Matrix Routing v8 OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi) (v8.47.0, retrieved July 2026): | Mode | Sync limit | Async limit | Live traffic & custom options | Region constraint | | ------------------------------------------------------------- | ----------------------------- | --------------- | ----------------------------- | ------------------- | | **Flexible** (`regionDefinition: world`) | 15×100 or 100×1 | 15×100 or 100×1 | Yes | Unlimited | | **Region** (`circle`, `boundingBox`, `polygon`, `autoCircle`) | 500×500 | 10,000×10,000 | Yes | Max 400 km diameter | | **Profile** (`world` + `profile`) | 500×500, or 1×2000, or 2000×1 | 10,000×10,000 | **No** | Unlimited | Read that table before you architect. A truck-constrained matrix with live traffic and custom avoidances runs in **Flexible** mode and caps at **15 × 100**. The 10,000 × 10,000 ceiling requires either a bounded region under 400 km diameter, or Profile mode — which disables custom options and live traffic entirely. **"HERE supports 10,000 × 10,000 matrices" is true and misleading.** It is not true for the configuration a fleet dispatcher needs. Additional documented constraints: request body maximum 10 MiB uncompressed; a maximum of 100 waypoints matched to any single road segment across origins and destinations combined; when only `origins` is supplied it is reused as `destinations`, capping origins at 50 in that case. Google's `computeRouteMatrix` accepts large vehicle profiles and imposes its own element limits, which vary by endpoint and tier. **Verify current limits directly** at [Google's Routes API documentation](https://developers.google.com/maps/documentation/routes) rather than from any comparison table, including this one. The architecturally useful statement is directional: HERE's Region and Profile async modes reach matrix sizes at which looping point-to-point routing is not merely expensive but infeasible. Whether that helps you depends on whether your workload tolerates a bounded region or the loss of live traffic. ### Pricing We will not publish a savings percentage. Cost outcome depends on your API mix, monthly volume, region, contract terms, billing SKU, whether you batch, and whether you cache. Those variables span more than an order of magnitude. A single percentage claiming to summarize them is not a comparison. What can be said structurally: * Both platforms bill per request for routing. * Both have volume tiers and free bundles. Bundles are per-API and do not pool. * **On HERE, a truck route is a routing request.** It draws from the same free allowance and the same rate as a car route. The vehicle profile does not move it to a different meter. * **On Google, large vehicle routing requires `routingPreference=TRAFFIC_AWARE_OPTIMAL`,** which bills above the Compute Routes Essentials rate. Confirm the exact SKU your traffic will land on before modelling it. * HERE offers an **asset-based** commercial model in addition to call-volume, priced per tracked vehicle rather than per call. Availability depends on contract tier — confirm before it becomes load-bearing. * Google has repriced Maps Platform more than once since 2018. Whether HERE pricing through a partner is contractually more stable is a term in your contract, not a property of the platform. For current Google rates see [Google Maps Platform pricing](https://developers.google.com/maps/billing-and-pricing/pricing). For HERE rates via Placematic, the [pricing calculator](https://placematic.com/here-location-services/here-pricing/) produces a range. For the honest cost method, see [Reducing Google Maps Costs](/use-cases/reducing-google-maps-costs). ## Request and billing architecture The single largest cost determinant is not the rate. It is whether you chose the right primitive. ```mermaid theme={null} theme={null} flowchart TD Q{What do you need?} Q -->|A path a vehicle follows| R[Routing] Q -->|A table of travel costs| M{How many pairs?} Q -->|An ordered itinerary
across a fleet| T[Tour Planning] M -->|One| R M -->|More than one| MX[Matrix] R --> L{Inside a loop?} L -->|Yes| MX L -->|No| OK[Correct] ``` An n×m cost table is n·m routing calls, or one matrix call. That is a complexity difference. It is not closed by any vendor's rate card, and it applies identically on Google. See [Routing vs Matrix](/architecture/choosing-routing-vs-matrix). ## Migration mapping | Google | HERE | Notes | | ------------------------------------------------- | -------------------------------------- | ------------------------------------------------------------------ | | Routes API `computeRoutes` | Routing v8 `/v8/routes` | HERE takes `origin=lat,lng`; coordinate order differs from GeoJSON | | Routes API `computeRouteMatrix` | Matrix Routing v8 `POST /v8/matrix` | HERE returns **flat row-major arrays**, not nested objects | | `travelMode=TRUCK` + `routeModifiers.vehicleInfo` | `transportMode=truck` + `vehicle[...]` | **Units differ. Verify both.** See warning below | | `routingPreference=TRAFFIC_AWARE_OPTIMAL` | `departureTime` | HERE's `departureTime: any` disables traffic awareness entirely | | Hazardous goods classification | `shippedHazardousGoods` (11 values) | Different taxonomies. Re-map deliberately and have it reviewed | | Route Optimization API | Tour Planning | Compare on your problem shape, not on product names | | — | `weightPerAxleGroup` | Verify Google equivalent before assuming absence | | — | `tunnelCategory` (ADR B/C/D/E) | No documented Google equivalent | | — | Toll Cost | Google documents truck toll prices as unsupported | | — | Route Matching | No Google equivalent | **Units are the most likely migration defect in either direction.** HERE expresses vehicle height in **centimetres** — `height=410` for a 4.1 m vehicle. Google's large vehicle field names indicate **millimetres** — `totalHeightMm`. Get the conversion wrong by a factor of ten and you have declared either a 41-metre vehicle that routes nowhere, or a 41-centimetre vehicle that routes everywhere. The second case returns `200`, produces plausible routes, and passes review. Convert units at each adapter, never in the shared interface. Assert the converted value in a test, not the input. **Semantic differences that will bite you:** **`200` is not success.** HERE returns HTTP `200` with an empty `routes` array and a `notice` containing `routeCalculationFailed` when no path exists. Checking `resp.ok` swallows the failure. **Both platforms can return a route that violates a constraint, and both tell you.** HERE returns per-cell `errorCodes` in matrix responses, including `3` — route found but violates a restriction. Google flags routes where restrictions were partially ignored. **Read both.** A route returned with a violation flag is not a route. **Matrix results are flat and row-major.** A 2×2 matrix returns `travelTimes: [73, 1231, 983, 400]`. Index as `travelTimes[origin * numDestinations + destination]`. Get it wrong and every travel time is plausible and every assignment is transposed. Nothing throws. **`403` is not `401`.** `401` means the credential is wrong. `403` means the credential is valid and your account lacks entitlement for that product. Retrying `403` is permanently futile. On Google, an unprovisioned large vehicle routing project behaves the same way — it is an entitlement problem, not a code problem. **Follow the `statusUrl` HERE returns.** HERE's specification explicitly warns that constructing the async status URL from the base host may resolve to a different region and return `404`, because of global load balancing. See [Google Migration Architecture](/architecture/google-migration-architecture). ## Architecture implications **Caching.** Both platforms benefit identically from caching route geometry (stable for hours) separately from ETAs (not stable). Neither is a differentiator. **Batching.** HERE's matrix async mode is a job: submit, persist the `matrixId`, poll the returned `statusUrl`, retrieve. This is state your architecture must hold across a worker restart, or you resubmit and pay twice. Google's matrix is synchronous, which is simpler and imposes lower size ceilings. **Vendor abstraction.** If you build a provider facade, **design its interface from the richer constraint set.** An interface derived from Google's large vehicle profile has a field for gross weight and axle count, but none for `weightPerAxleGroup` or `tunnelCategory`. When you add HERE, those constraints have nowhere to live and get dropped silently. This is the most common structural error in Google-to-HERE migrations, and Large Vehicle Routing has made it **harder to notice**, not easier. A facade built from an unconstrained Google interface was obviously incomplete — it had no vehicle at all. A facade built from Google's large vehicle profile looks complete. It has dimensions, weight, axles, hazmat. The two or three fields it cannot carry are exactly the ones that determine legality in the jurisdictions that regulate most tightly. **Failover.** Falling back from HERE truck routing to Google Large Vehicle Routing is **more dangerous than falling back to unconstrained routing was.** An unconstrained fallback produced obviously wrong routes — a truck sent under a low bridge fails visibly and immediately. A partially-constrained fallback produces plausible ones. If your vehicle's legality depends on axle-group weight, tunnel category, or a cargo class the fallback cannot express, the route looks correct, passes review, and is illegal. Refuse to route rather than route with a degraded constraint set. Assert field-level parity in the facade, not merely that a provider responded. ## Cost model What creates billable activity, on both platforms: * One routing call per computed path * One matrix call per submitted matrix (HERE), or per element batch (Google — verify current element accounting) * Requesting turn-by-turn instructions when you consume only a duration * Recomputing routes for unchanged origin, destination, and departure time * Re-routing on every GPS ping to refresh an ETA **Request multiplication risks:** The loop is the risk. A store locator ranking eight candidates, a dispatcher assigning forty couriers, a territory system scoring eight hundred ZIP centroids — each of these is one matrix call and is routinely implemented as n routing calls. **Total cost of ownership** includes the engineering time to express constraints your platform does not support, and the cost of the incidents caused by the ones you approximated. A team implementing axle-group bridge formula logic on top of a gross-weight scalar is paying salaries to badly reproduce a federal regulation. ## How to evaluate with your own data Do not accept the tables above. Run this. **Zero: confirm access.** Google documents Large Vehicle Routing as provisioned on request. Confirm your project is enabled before you plan the evaluation, not during it. **Route set.** Take 500 trips your fleet actually drove, with telematics ground truth for actual driven distance and duration. Route each through both platforms. **Compare against ground truth, not against each other.** Two wrong answers can agree. **Report the residual distribution, not the mean.** A platform whose durations are unbiased on average but have twice the variance produces twice as many late deliveries. **Truck constraint validation is a gate, not a metric.** Route a vehicle with `height: 410` (HERE) / `totalHeightMm: 4100` (Google) through: * The 11foot8 bridge, Durham NC * Storrow Drive, Boston MA * The Southern State Parkway, Long Island NY Any path returned is a failure on either platform. Run the same three in car mode as a control — all three must route, proving the test exercises the constraint rather than failing for an unrelated reason. **Watch the units, and watch the flags.** Send `4100` to HERE and you have declared a 41-metre vehicle. Send `410` to Google and you have declared a 41-centimetre one, which will route through all three traps and lead you to conclude — wrongly — that Google has no constraint handling. Also check the response for a restriction-violation flag on both platforms. A returned path with a violation flag is a failure, not a pass. **Constraint tail.** Beyond the trap geometry, test the constraints your operation actually depends on: axle-group weight if you are regulated by it, tunnel category if you carry ADR cargo, and a cross-border lane if you run one. These are the tests that separate the platforms now. Dimension traps no longer do. **Departure times.** Peak and off-peak, on the corridors you actually drive. A 3am matrix applied to a 5pm dispatch is fiction. **Request mix.** Instrument your current per-endpoint call counts from logs. Not estimates. Then price the same counts on both platforms, after applying caching and batching to both — and confirm which Google SKU your traffic lands on, since large vehicle routing does not bill at the Essentials rate. Optimize on your current platform first — cache, batch, debounce, deduplicate — and re-measure. A meaningful share of teams find the bill halves without a vendor change. What remains is your real migration case, with a clean baseline. ## Common decision mistakes **Assuming Google has no truck routing.** It has, since August 2026, in the 48 contiguous United States: dimensions, gross weight, axle count, hazmat classes. Any comparison written before that date — including earlier revisions of this page — is wrong on this point. **Assuming the constraint sets are therefore equivalent.** Toll cost, speed limits, radioactive cargo, ADR tunnel category and non-US coverage are documented gaps. Per-axle-group weight requires verification. **Evaluating Google LVR without checking provisioning.** Generally available is not the same as enabled on your project. **Treating Google as a safe failover for truck routing.** A partially-constrained fallback fails silently. That is worse than a fallback that fails loudly. **Passing millimetres where centimetres are expected, or the reverse.** A 41-centimetre truck routes anywhere and returns `200`. **Ignoring the restriction-violation flag.** Both platforms will tell you the route breaks a constraint. Neither will stop you shipping it. **Comparing per-1,000 rates.** Close at entry level. Meaningless without the SKU, the call mix, and the caching strategy. **Treating matrix size as a single number.** It varies by mode, by sync/async, and by entitlement. **Assuming truck routing is a parameter.** `transportMode=truck` selects an engine. It does not describe your vehicle. Omit dimensions and HERE routes an unconstrained vehicle with no warning. **Designing the abstraction layer from the narrower constraint set.** **Migrating before optimizing.** Moving waste to a cheaper meter. **Migrating everything simultaneously.** Two changes, one incident, ambiguous cause. **Validating cost but not route quality.** A cheaper wrong route is worse. **Presenting savings without migration engineering cost.** ## Choose HERE when * You route commercial vehicles outside the contiguous United States * Your jurisdiction regulates by axle group * You carry ADR-regulated or radioactive cargo * You need toll cost for the described vehicle before dispatch * You need defensible reconstruction of driven routes for compliance * You need fleet-wide multi-stop optimization under capacity and time-window constraints * You need very large matrices and can accept a bounded region or Profile mode * Contractual price stability is a board-level concern *and you have negotiated it* ## Choose Google when * Your vehicles are passenger cars * Your commercial vehicle operation is US-domestic, always online, and does not need toll cost at dispatch * Your product is consumer-facing and users recognize the map * Driver familiarity with the Google Maps interface is a material adoption constraint * Your volume is low enough that migration engineering exceeds the saving * Ecosystem familiarity and hiring are material constraints * You need Street View or comparable imagery ## Related documentation Constraints, units, and the trap geometry that proves they apply. Why the toll figure depends on the vehicle profile, and what Google returns instead. Modes, async lifecycle, and the flat result array. Dual-running, shadow comparison, rollback. Also: [Migrating from Google Maps](/guides/google-migration) · [Reducing Google Maps Costs](/use-cases/reducing-google-maps-costs) · [Cost Optimization Patterns](/architecture/cost-optimization-patterns) · [HERE vs Google Maps](/comparisons/here-vs-google-maps) ## Sources **HERE** * [Routing API v8 developer guide](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/get-started.html) * [Matrix Routing v8 OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi) — authoritative on modes and size limits * [Transport modes](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/topics/transport-modes.html) * [Tour Planning introduction](https://docs.here.com/tour-planning/docs/introduction-tour-planning) **Google** * [Routes API documentation](https://developers.google.com/maps/documentation/routes) * [Large vehicle routing overview](https://developers.google.com/maps/documentation/routes/lvr) — vehicle parameters, coverage, documented limitations * [Maps Platform pricing](https://developers.google.com/maps/billing-and-pricing/pricing) **Placematic** * [Commercial comparison and pricing overview](https://placematic.com/compare/here-routing-vs-google-maps/) *HERE specification details verified July 2026 against Matrix Routing v8.47.0. Google Large Vehicle Routing capabilities, coverage and documented limitations verified August 2026. Limits, deprecations and pricing change; verify against primary sources before architecting.* *** Need to compare these platforms with your own request mix? Placematic can help you run a technical and cost evaluation using representative routes, addresses and production volumes. Placematic is an official HERE Technologies reseller and implementation partner. [Cost Reduction Audit](https://placematic.com/here-location-services/cost-reduction-audit/). # HERE vs Azure Maps Source: https://docs.placematic.com/comparisons/here-vs-azure-maps A procurement and enterprise-architecture decision more than an API decision — and a note on where Azure Maps data comes from. # HERE vs Azure Maps Before comparing capabilities, one structural fact deserves stating. **Microsoft and HERE have a long-standing commercial relationship, and Azure Maps has historically incorporated HERE data.** The precise scope of that relationship, and which services it currently covers, is a matter for Microsoft's and HERE's own documentation rather than ours. Verify the current data provenance of Azure Maps against [Microsoft's documentation](https://learn.microsoft.com/en-us/azure/azure-maps/) before assuming either that it is HERE data or that it is not. This has changed over time and we will not characterize it from memory. The practical consequence: this comparison is **less about map data quality** than most vendor comparisons, and **more about enterprise architecture, procurement, roadmap dependency, and who you have a relationship with.** ## Short verdict **Choose Azure Maps when** your organization is deeply committed to Azure, when Entra ID and Azure RBAC governance is a requirement rather than a preference, when procurement through an existing Microsoft Enterprise Agreement removes months from your timeline, and when your location requirements are within its published capability set. **Choose HERE directly when** you need capabilities Azure Maps does not expose — commercial vehicle constraint depth, fleet-scale VRP optimization, map matching for compliance — or when you need a direct relationship with the party responsible for the underlying data. ## Comparison scope Azure Maps is a **cloud platform service**. HERE Location Services is a **location platform**. They overlap substantially and are not the same category. This page covers routing, geocoding, maps, traffic, identity, procurement, and roadmap dependency. It does not attempt to enumerate every Azure Maps endpoint; verify against Microsoft's current documentation. ## Decision summary | Requirement | Better fit | Why | | -------------------------------------------- | ---------- | ------------------------------------------------------------ | | Azure-native identity and RBAC | Azure Maps | Entra ID integration, managed identities, Azure Policy | | Procurement through an existing Microsoft EA | Azure Maps | Often the decisive factor | | Azure ecosystem integration | Azure Maps | Event Grid, Functions, Synapse, Fabric | | Commercial vehicle constraint depth | HERE | Verify Azure Maps' current truck parameter set independently | | Fleet-wide VRP optimization | HERE | Tour Planning solves capacity, time windows, priorities | | GPS trace → road segments for compliance | HERE | Route Matching | | Direct relationship with the data provider | HERE | You contract with the source | | Roadmap and deprecation control | HERE | See below | | Data residency within your Azure regions | Azure Maps | If that is a hard requirement | | Support escalation path | Depends | Whose contract, whose SLA | ## Where Azure Maps is stronger ### Identity and governance This is the genuine advantage and it is not small. Azure Maps integrates with Microsoft Entra ID. That means managed identities instead of API keys, Azure RBAC for authorization, Azure Policy for compliance enforcement, and audit logging through the same pipeline as everything else in your subscription. For an organization whose security review requires that every service authenticate through Entra ID and every access decision be expressible in Azure RBAC, this is not a preference. It is the requirement, and no amount of routing capability offsets it. Compare this to holding a static HERE API key in a secret manager, rotating it manually, and building your own per-tenant authorization layer. ### Ecosystem integration Event Grid for geofence events. Azure Functions for serverless processing. Synapse or Fabric for spatial analytics. Azure Monitor for observability, in the same pane as everything else. If your data platform is Azure, the integration cost of an Azure-native location service is genuinely lower. ### Procurement An existing Microsoft Enterprise Agreement can eliminate a vendor onboarding process, a security review, a legal review, and a procurement cycle. **This is often the decisive factor and it is rarely written down.** An architecture that is 15% better on API capability and requires six months of vendor onboarding loses to one that is available on Tuesday under an existing contract. Say this out loud in the design review. It is a legitimate reason. ### Data residency Azure Maps runs in Azure regions. If your compliance regime requires processing within a specific geography and you have already solved that for Azure, you have solved it for Azure Maps. ## Where HERE is stronger ### Commercial vehicle routing depth HERE Routing v8 exposes vehicle height, width, length, gross weight, current weight, weight per axle, weight per **axle group** (single, tandem, triple, quad, quint), axle count, trailer count, ADR tunnel category, eleven hazardous cargo types, and KPRA length for California and Idaho restrictions. Verified against the [HERE Matrix Routing v8 OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi), v8.47.0, July 2026. Azure Maps offers truck routing parameters. **Compare its current parameter set directly** against [Microsoft's routing documentation](https://learn.microsoft.com/en-us/azure/azure-maps/) rather than accepting any comparison table, including this one. The question to ask is specific: *can it express weight per axle group?* If your jurisdiction regulates by axle group and the API cannot express it, the API cannot route your vehicle legally, regardless of how many other truck parameters it has. ### Fleet-scale optimization HERE Tour Planning is a Vehicle Routing Problem solver: capacitated VRP, time windows, multi-depot, heterogeneous fleets, priorities, pickup-and-delivery, reloads. Verify whether Azure Maps exposes an equivalent solver. Waypoint optimization within a single route is a different problem — it orders stops for one vehicle rather than assigning stops across a fleet under constraints. ### Map matching for compliance Route Matching returns the road segments a vehicle actually travelled, with attributes, which is what IFTA jurisdiction miles and speed compliance require. ### Direct relationship with the data provider If Azure Maps incorporates HERE data, then a routing defect traced to map data has an escalation path through Microsoft to HERE. Contracting with HERE directly — or through a Gold Partner — shortens that path. Whether this matters depends on whether a wrong route is an inconvenience or a bridge strike. ## Roadmap dependency This is the risk that does not appear on a feature matrix. **A cloud provider's location service is a product in a portfolio.** Its roadmap is set by priorities you do not see, and its lifecycle is governed by that provider's deprecation policy. Location is HERE's entire business. It is one service among hundreds for a hyperscaler. Read the current lifecycle and deprecation policy for Azure Maps and for each of its constituent APIs before committing. Cloud services get retired, versioned, and repositioned. A location platform whose vendor's core business is location has different incentives from one whose vendor's core business is compute. This is not a criticism of Microsoft. It is a structural observation, and it applies equally to any hyperscaler location service. Verify at [Microsoft's Azure Maps documentation](https://learn.microsoft.com/en-us/azure/azure-maps/) and their product lifecycle pages. ## Architecture implications The choice changes your identity model, your observability, and your procurement path. It changes your spatial architecture very little. ```mermaid theme={null} flowchart TB subgraph AzurePath["Azure Maps path"] ENTRA[Entra ID
managed identity] AM[Azure Maps] EG[Event Grid] MON[Azure Monitor] end subgraph HerePath["HERE path"] SEC[Secret manager
API key rotation] GW[Your gateway:
authz, quota,
cost attribution] HERE[HERE APIs] OBS[Your observability] end subgraph Both["Identical either way"] PG[(PostGIS:
materialized zones,
geocode cache,
geofence containment)] APP[Application logic] end ENTRA --> AM --> EG AM --> MON SEC --> GW --> HERE GW --> OBS AM --> APP HERE --> APP PG --> APP ``` **Notice the bottom box.** Materialized delivery zones, cached geocodes, and geofence containment are yours regardless of vendor, and they are most of what a location system does. See [Cost Optimization Patterns](/architecture/cost-optimization-patterns). **With Azure Maps** you inherit Entra ID authentication and Azure Monitor observability. You save building a gateway for authorization. **With HERE** you build the gateway anyway — for per-tenant quota, cost attribution, and priority queuing. Which you need in a multi-tenant platform regardless. See [Multi-Tenant Location Platform](/architecture/multi-tenant-location-platform). If you are building a multi-tenant SaaS product, you will build the gateway either way. The Entra ID advantage shrinks. If you are building an internal enterprise application, the gateway is pure overhead you avoid with Azure Maps. That distinction predicts the right answer more reliably than any API comparison. ## Cost model **Azure Maps** bills through your Azure subscription, against your existing commitment, potentially drawing down committed spend you have already agreed to. That is a real financial advantage that has nothing to do with rate cards. **HERE** bills separately. Through a Gold Partner, one contract and one invoice. We will not publish a cost comparison. It depends on your Azure commitment, your discount tier, your API mix, and your volume. An organization with unspent Azure commitment faces a different marginal cost than one without. Model both against your actual instrumented call counts. Current pricing: [Azure Maps pricing](https://azure.microsoft.com/en-us/pricing/details/azure-maps/) · [Placematic HERE pricing](https://placematic.com/here-location-services/here-pricing/). **Total cost of ownership** for Azure Maps includes lower integration cost if you are already Azure-native. For HERE it includes a gateway you may need to build regardless. ## Migration considerations **Both directions are moderate.** Response schemas differ. Coordinate ordering must be checked at every adapter. Neither is a rewrite of your spatial core, because your spatial core should be PostGIS. **Feature gaps are the real migration risk.** If you migrate from HERE to Azure Maps and lose weight-per-axle-group expression, you have not migrated. You have shipped a system that produces illegal routes. **Build a provider facade, designed around the richer constraint set.** An interface derived from the weaker platform has no field for the constraint the stronger one expresses. When you add it back, the constraint has nowhere to live and gets silently dropped. **Dual-run and compare against ground truth.** See [Google Migration Architecture](/architecture/google-migration-architecture) — the methodology is vendor-agnostic. ## How to evaluate with your own data **Answer the procurement question first.** If an existing Microsoft EA removes six months from your timeline and Azure Maps meets your requirements, the technical comparison may be academic. Establish this before you spend engineering time. **Then test the capability gate.** Take the constraint your business genuinely requires — for many fleet operators, weight per axle group — and verify each platform expresses it. This is a yes/no question and it takes an hour. **Truck constraint gate.** Route a 410 cm vehicle through the 11foot8 bridge (Durham NC), Storrow Drive (Boston), and the Southern State Parkway (Long Island). Any path returned is a failure. Run the same three in car mode as a control. **Routing quality.** 500 real historical trips through both, compared against telematics ground truth. Report residual distributions. **Identity.** Prototype the auth flow. If Entra ID integration removes a security review, that is a measurable saving. **Roadmap.** Read the deprecation policy for every API you plan to depend on. For both vendors. ## Common decision mistakes **Comparing API features while ignoring procurement.** Procurement usually wins, and it should be acknowledged rather than reverse-engineered into a technical justification. **Assuming Azure Maps data is or is not HERE data.** Verify. The relationship has changed over time. **Assuming a hyperscaler location service has a hyperscaler's roadmap commitment.** Read the lifecycle policy. **Designing the abstraction layer from the weaker constraint set.** **Migrating away from a platform that expresses a constraint you legally require.** **Ignoring the gateway you will build anyway** in a multi-tenant product, which shrinks the Entra ID advantage. **Treating identity integration as a nice-to-have** when your security review treats it as a gate. **Assuming unspent Azure commitment is free money.** It is committed spend. It is also real. ## Choose Azure Maps when * Entra ID and Azure RBAC governance is a requirement * Procurement through an existing Microsoft EA materially accelerates delivery * Your data platform is Azure and integration cost matters * Data residency within your Azure regions is a hard requirement * Your location requirements sit comfortably within its published capability set * You are building an internal enterprise application, not a multi-tenant SaaS product ## Choose HERE when * You need commercial vehicle constraint depth that Azure Maps does not express * You need fleet-wide VRP optimization * Compliance requires defensible map matching * You want a direct relationship with the party responsible for the map data * Roadmap stability for a location-specific platform matters to a multi-year build * You are building the gateway regardless, so the identity advantage is smaller than it appears ## Related documentation The gateway you build regardless, and why it shrinks the Entra ID advantage. The constraint set to test both platforms against. Most of what a location system does never touches a location API. API keys, rotation, and what you build when identity is not federated. Also: [Tour Planning](/guides/tour-planning) · [Route Matching](/guides/route-matching) · [HERE vs Google Maps](/comparisons/here-vs-google-maps) ## Sources **Microsoft** * [Azure Maps documentation](https://learn.microsoft.com/en-us/azure/azure-maps/) * [Azure Maps pricing](https://azure.microsoft.com/en-us/pricing/details/azure-maps/) **HERE** * [Routing API v8](https://www.here.com/docs/category/routing-api-v8) * [Matrix Routing v8 OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi) * [Tour Planning](https://docs.here.com/tour-planning/docs/introduction-tour-planning) **Placematic** * [Commercial comparison overview](https://placematic.com/compare/here-vs-azure-maps/) *Verified July 2026. Azure Maps capabilities, data provenance, pricing and lifecycle policy change. Verify against Microsoft's current documentation before architecting.* *** Need to compare these platforms with your own request mix? Placematic can help you run a technical and cost evaluation using representative routes, addresses and production volumes. Placematic is an official HERE Technologies reseller and implementation partner. [Cost Reduction Audit](https://placematic.com/here-location-services/cost-reduction-audit/). # HERE vs Google Maps Source: https://docs.placematic.com/comparisons/here-vs-google-maps Platform-level fit. Consumer applications versus B2B operational systems, and why the answer is usually both. # HERE vs Google Maps **Google Maps Platform is built for applications about places. HERE Location Services is built for systems about movement** — particularly the movement of commercial vehicles across jurisdictions. That sentence predicts most of the differences below. ## Short verdict **Choose Google when** your users interact with the map, when place data is your product, or when ecosystem familiarity is a constraint you cannot afford to fight. **Choose HERE when** your system makes operational decisions a professional acts on — dispatching a truck across a state line, assigning a technician, reconstructing a route for an auditor. **Choose both when** you are doing both. Most B2B products with a consumer surface are, and pretending otherwise produces a worse product to satisfy a slide about vendor consolidation. **Updated August 2026.** Google Maps Platform added Large Vehicle Routing on 17 August 2026 — a commercial vehicle profile for the 48 contiguous United States. Earlier revisions of this page stated Google had no such capability. That statement is no longer true and has been corrected throughout. The platform-level verdict above did not change. The reasons behind it did. ## Comparison scope This is the platform-level page: overall fit, data, ecosystem, licensing, support, migration complexity. It does not duplicate the detailed comparisons: * [HERE Routing vs Google Maps](/comparisons/here-routing-vs-google-maps) — including the full Large Vehicle Routing constraint comparison * [HERE Geocoding vs Google Maps](/comparisons/here-geocoding-vs-google-maps) ## Decision summary | Requirement | Better fit | Why | | -------------------------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------- | | Consumer place discovery | Google | Business database, hours, reviews, photos | | Street View or comparable imagery | Google | HERE has no equivalent | | Commercial vehicle routing, US domestic, dimensions and weight | Comparable | Google Large Vehicle Routing expresses these since August 2026 | | Commercial vehicle routing outside the contiguous US | HERE | Google LVR covers the 48 contiguous states only | | Toll cost for a described commercial vehicle | HERE | Google documents truck toll prices as unsupported | | Hazardous cargo routing | HERE | Full ADR set including radioactive; tunnel category is a parameter | | GPS trace → road segments | HERE | Route Matching. No Google equivalent | | Fleet-wide multi-stop optimization | HERE | Tour Planning is a VRP solver; compare on problem shape, not product name | | Renderable transport restriction layers | HERE | Restrictions as map attributes, not only as routing behaviour | | Map styling depth | Both adequate | See [HERE vs Mapbox](/comparisons/here-vs-mapbox) if styling is the requirement | | Address resolution at volume | Comparable | Test on your addresses | | Mobile turn-by-turn with offline maps | HERE | HERE SDK offers on-device offline. Google Navigation SDK supports large vehicle routes online, by route token | | Ecosystem, hiring, community | Google | Materially larger developer population | | Contractual price stability | A contract term | Not a platform property | ## Where HERE is stronger ### Commercial vehicle operations Routing that respects vehicle height, gross weight, per-axle weight, **axle groups**, hazardous cargo type, and ADR tunnel category. Google Maps Platform exposed none of this until August 2026, when Large Vehicle Routing added height, width, length, gross weight, axle count and hazardous goods classification for the contiguous United States. What separates the platforms now is the tail of the constraint set, not its existence: per-axle-group weight, the full ADR cargo set including radioactive materials, toll cost, posted speed limits, and coverage beyond the contiguous 48. **The gap is jurisdictional and cargo-specific, not absolute.** A 4.1 m trailer under a 3.4 m clearance in Ohio is handled by both platforms. The same trailer entering Ontario is handled by one. A five-axle tractor-trailer whose legality is set by the federal bridge formula — written per axle *group*, not per axle — is handled by whichever platform can express the group. Two questions settle the platform choice before any benchmark: **does your operation leave the contiguous 48, and does your jurisdiction regulate by axle group.** ### Toll cost for a described vehicle HERE returns expected toll cost computed against the vehicle you described — class, axle count, dimensions, weight. Google documents truck toll prices as unsupported for large vehicle routing. The figure is not a lookup. The same origin and destination returns a materially different toll depending on whether the vehicle profile was supplied — a passenger-class estimate and a commercial one are different numbers for the same road. If your dispatcher prices a load before the truck moves, this is not a reporting feature. It is the quote. See [Toll Cost](/guides/toll-cost). ### Compliance-grade route reconstruction Route Matching returns the road segments a vehicle actually travelled, with attributes. IFTA jurisdiction miles and speed compliance against posted limits both require this. Reverse geocoding does not substitute; it returns an address, not a segment, and it will not survive an audit. Google has no equivalent product, and Large Vehicle Routing does not change this. ### Fleet optimization Tour Planning solves the Vehicle Routing Problem: capacity, time windows, multi-depot, heterogeneous fleets, priorities, pickup-and-delivery. HERE documents it as included in the Base Plan. Google's Route Optimization API now accepts large vehicle profiles, which narrows this gap. Compare the two on your own problem shape rather than on product names: does the solver assign jobs across a fleet under capacity, windows and skills, and how does it report the jobs it could not assign. ### Transport-attributed map data Weight restrictions, height limits, hazmat prohibitions, designated truck routes, and truck-relevant POIs are present in HERE's base data and available as renderable layers. Google applies equivalent restriction data inside Large Vehicle Routing in the contiguous US, but surfaces it as routing behaviour rather than as queryable map attributes or renderable restriction layers. **If your dispatcher needs to see the restriction on a map, not merely route around it, that difference is operational.** ### Coverage HERE carries commercial road attributes in more than 200 countries. Google Large Vehicle Routing stops at the contiguous 48. For a carrier running US–Canada or US–Mexico lanes, this is not a feature comparison. It is a coverage boundary that ends the evaluation. ### Commercial model options HERE offers an asset-based model — priced per tracked vehicle rather than per call — alongside call-volume pricing. A dispatcher who reroutes 200 trucks forty times a day is punished by call-volume pricing for operational diligence. Asset-based pricing may invert that. Availability depends on contract tier; confirm before it becomes load-bearing in a business case. ## Where Google is stronger ### Place and business data **For business names, hours, reviews, photos, and category coverage, Google's data is categorically better.** Placematic sells HERE. We would rather you know this before a migration than after. If your product's value is helping people find a restaurant, a clinic, or a shop, that surface belongs on Google. ### Consumer autocomplete Ranking, aliasing, and typo tolerance built from consumer query volume HERE does not have. ### Street View No HERE equivalent. ### Ecosystem More engineers have shipped Google integrations. More Stack Overflow answers. More libraries assume it. Hiring is easier. This is a real cost. It appears in your engineering budget rather than on a rate card, which is precisely why it gets omitted from comparisons. ### Traffic for passenger vehicles Google's traffic derives from an enormous consumer device population. For dense urban car routing this is a genuine advantage. Large Vehicle Routing extends it with truck-observed speed data in the US, which is a real improvement over applying car speeds to a truck. **Whether it is an advantage on your corridors is empirical.** Test it. ### Driver-facing familiarity If your drivers already navigate with Google Maps, the adoption cost of a Google-rendered in-cab experience is close to zero. Driver adoption is a real project risk that no API comparison captures. ### Simplicity at low volume Below roughly ten thousand calls a month, both are effectively free and Google onboards faster. ## Where the difference is commercial or operational ### Pricing We will not publish a savings percentage. Cost outcome depends on API mix, monthly volume, region, contract terms, billing SKU, batching, and architecture. These span more than an order of magnitude. A single number claiming to summarize them is a slogan. **The meters do not translate.** Google bills autocomplete on a session-token model where sessions are used. HERE bills per request. Google prices some products per element, HERE per transaction. **You cannot derive a HERE forecast from a Google invoice.** One structural difference worth naming: **on HERE, a truck route is a routing request** — same rate and same free allowance as a car route. **On Google, large vehicle routing requires the traffic-aware optimal preference,** which bills above the Compute Routes Essentials rate. Confirm which SKU your traffic lands on before modelling it. Instrument per-endpoint call counts from your logs first. That is the only input that produces a defensible comparison. Current rates: [Google Maps Platform pricing](https://developers.google.com/maps/billing-and-pricing/pricing) · [Placematic pricing](https://placematic.com/here-location-services/here-pricing/). ### Price stability Google has repriced Maps Platform more than once since 2018. Whether HERE pricing is contractually more stable is a term in your contract, not a property of the platform. Negotiate it or do not claim it. ### Access and provisioning HERE entitlements are per-product and confirmed against your contract; a valid key without the entitlement returns `403`. Google documents Large Vehicle Routing as generally available but provisioned on request rather than enabled by default. **Generally available is not the same as enabled on your project.** Confirm before planning a sprint around it. ### Caching and storage terms Both vendors restrict retention of geocoded coordinates, and the restrictions differ by platform, plan, and time. **Get your answer in writing.** A geocode cache is the largest single cost lever available, and its permissibility is a contract term. ### Support HERE licensed through a Gold Partner means one contract, one invoice, and one accountable party for the integration — not merely for the API being up. Whether that matters depends on whether a wrong route is an inconvenience or a bridge strike. ## Technical comparison ### Maps and rendering Both serve vector and raster tiles. Both work with standard web mapping libraries; if you already render with Leaflet, OpenLayers, or MapLibre GL, integration with HERE tiles is largely a tile URL swap. If you are on the Google Maps JS SDK, the rendering layer is a rewrite. Tile aesthetics differ visibly. Show your designers both before you commit. This is a product review that happens either before the migration or after it. HERE offers transport-specific layers — weight and height restrictions — that Google does not expose as renderable map data, even where Large Vehicle Routing applies the same restrictions when computing a path. **Web tile access and mobile SDK access are separate entitlements on HERE.** Discovering this at app store submission is expensive. ### Mobile SDKs HERE SDK offers on-device turn-by-turn, offline maps, and on-device recalculation, including for truck profiles. Google's Navigation SDK supports large vehicle routes via a route token issued by the Routes API, keeping the dispatcher's plan and the driver's guidance consistent. It operates online. Verify current offline capability against [their documentation](https://developers.google.com/maps/documentation) rather than assumption. The most dangerous pattern in mobile fleet software: server-side truck routing with constraints, device-side recalculation without them. The driver deviates, the phone reroutes, and the constraint set never reached the app. Push vehicle profiles to the device. Test the recalculation path — including what happens when the device loses connectivity mid-route. ### Positioning HERE offers hybrid positioning — Wi-Fi, cellular, GNSS fusion — for environments where GPS fails. Most outdoor road-vehicle fleets do not need it and should not buy it. ## Architecture implications The platform choice changes less than most teams expect, because most of what a location system does should never touch a location API. ```mermaid theme={null} theme={null} flowchart TB subgraph Yours["Identical on either platform"] PG[(PostGIS:
materialized zones,
geocode cache,
geofence polygons)] GW[Gateway:
cache, quota, retry,
cost attribution] end subgraph Vendor["Both platforms"] API[Routing / Matrix /
Geocoding / Tiles] TRUCK[Truck dimensions
and gross weight] end subgraph Gap["Only HERE"] AXLE[Weight per axle group] TOLL[Truck toll cost] INTL[Truck routing outside
the contiguous US] RM[Route Matching] TP[Tour Planning VRP] end GW --> PG GW -->|cache miss| API GW --> TRUCK GW --> AXLE GW --> TOLL GW --> INTL GW --> RM GW --> TP ``` **Materialized delivery zones, cached geocodes, geofence containment, and lane-distance tables are yours regardless.** If you have not built them, your API bill is not evidence that you need a different vendor. See [Cost Optimization Patterns](/architecture/cost-optimization-patterns). **Vendor abstraction:** If you build a provider facade, **design its interface from the richer constraint set.** Large Vehicle Routing has made this error harder to notice, not easier. A facade built from an unconstrained Google interface was obviously incomplete — it had no vehicle at all. A facade built from Google's large vehicle profile looks complete: dimensions, weight, axles, hazmat. The two or three fields it cannot carry are exactly the ones that determine legality where regulation is tightest. This remains the most common structural error in Google-to-HERE migrations. **Failover.** Falling back from HERE truck routing to Google Large Vehicle Routing is **more dangerous than falling back to unconstrained routing was.** An unconstrained fallback produced obviously wrong routes — a truck sent under a low bridge fails visibly. A partially-constrained fallback produces plausible ones. If legality depends on axle-group weight, tunnel category, or a cargo class the fallback cannot express, the route looks correct, passes review, and is illegal. Refuse to route rather than route with a degraded constraint set. Assert field-level parity in the facade, not merely that a provider responded. ## Migration complexity By surface, roughly ascending: | Surface | Complexity | Notes | | ---------------------------- | ----------- | -------------------------------------------------------------- | | Batch geocoding | Low | Smallest blast radius, often largest saving. Start here | | Telematics reverse geocoding | Low | Backend, high volume, no UI | | Matrix operations | Medium | Flat row-major arrays. Async job lifecycle | | Routing (passenger) | Medium–high | Quality validation required, not just cost | | Routing (commercial vehicle) | Medium–high | Unit conversion and constraint-tail mapping. See warning below | | Autocomplete | Medium | User-facing. Be honest about quality | | Rendering | High | Most visible. Tile aesthetics differ. Migrate last | | Mobile SDK | High | Separate entitlement. Binary size. Offline strategy | **Commercial vehicle routing is a new migration surface as of August 2026, and it has a unit trap.** HERE expresses vehicle height in centimetres. Google's large vehicle field names indicate millimetres. A factor-of-ten error produces either a vehicle that routes nowhere, or a 41-centimetre vehicle that routes everywhere and returns `200`. Convert at each adapter, never in the shared interface. Assert the converted value in a test. Migrate one surface. Stabilize. Then the next. Never migrate two simultaneously. Two independent changes, one production incident, ambiguous root cause. You will spend more on the investigation than you saved that month. See [Google Migration Architecture](/architecture/google-migration-architecture) for dual-running and rollback mechanics. ## Cost model **What creates billable activity, on both:** * Recomputing routes for unchanged inputs * Building cost tables with loops instead of matrix calls * Reverse-geocoding GPS packets rather than detected stops * Undebounced autocomplete * Re-geocoding stable customer addresses * Requesting turn-by-turn instructions when you consume a duration * Tiles without a CDN **Every one of those is platform-independent.** Fix them before you evaluate. Optimize on your current platform first — cache, batch, debounce, deduplicate — and re-measure. A meaningful share of teams find the bill halves without a vendor change. What remains is your real migration case, and now you have a clean baseline. See [Reducing Google Maps Costs](/use-cases/reducing-google-maps-costs). **Total cost of ownership** includes the engineering time to approximate capabilities your platform does not have, and the cost of the incidents caused by the ones you approximated badly. A team implementing axle-group bridge formula logic on top of a gross-weight scalar is paying salaries to reproduce a federal regulation without the data to do it. ## How to evaluate with your own data **Instrument first.** Per-endpoint call counts from logs. Not estimates. **Optimize on the incumbent.** Cache, batch, debounce. Re-measure. Present that saving before proposing a migration. **Confirm access.** If Google Large Vehicle Routing is in scope, confirm your project is provisioned before the evaluation, not during it. **Routing quality:** 500 real historical trips through both platforms, compared against telematics ground truth — not against each other. Report residual distributions, not means. **Truck constraint gate.** Route a 4.1 m vehicle through the 11foot8 bridge (Durham NC), Storrow Drive (Boston), and the Southern State Parkway (Long Island). Any path returned is a failure. Run the same three in car mode as a control. Send `410` to HERE and `4100` to Google — a unit error will make either platform look like it has no constraint handling at all. **Constraint tail.** Dimension traps no longer separate these platforms. Test what does: axle-group weight if you are regulated by it, ADR tunnel category if you carry regulated cargo, a cross-border lane if you run one, and toll cost if you quote freight. **Geocoding:** 1,000 of your own addresses, including rural, apartment, and previously-failed cases. Measure match rate, rooftop fraction, positional error distribution, and confidence calibration separately. **Rendering:** show your designers both, at your zoom levels, with your data overlaid. **Cost:** price your instrumented call counts on both, after applying caching and batching to both — and confirm the Google SKU, since large vehicle routing does not bill at the Essentials rate. ## Common decision mistakes **Assuming Google has no commercial vehicle routing.** It has, since August 2026, in the 48 contiguous United States. Any comparison written before that date — including earlier revisions of this page — is wrong on this point. **Assuming the constraint sets are therefore equivalent.** Toll cost, speed limits, radioactive cargo, ADR tunnel category and non-US coverage are documented gaps. Per-axle-group weight requires verification. **Treating Google as a safe failover for truck routing.** A partially-constrained fallback fails silently, which is worse than one that fails loudly. **Evaluating Large Vehicle Routing without confirming provisioning.** **Comparing rate cards.** Close at entry level. Determined by call mix, tier, batching, and caching. **Deriving a HERE forecast from a Google invoice.** The meters do not translate. **Migrating before optimizing.** Moving waste to a cheaper meter. **Migrating everything at once.** **Designing the abstraction layer from the narrower constraint set.** **Validating cost but not quality.** A cheaper wrong route is worse. **Assuming HERE POI data satisfies a consumer search feature.** It does not. **Treating tile styling as cosmetic.** It is, until the product review. **Presenting savings without migration engineering cost.** Your CFO will ask. **Forcing vendor consolidation.** A hybrid is a decision, not an admission. ## Choose Google when * Consumer place discovery is your product * Street View or comparable imagery is required * Your commercial vehicle operation is US-domestic, always online, and does not need toll cost at dispatch * Driver familiarity with the Google Maps interface is a material adoption constraint * Volume is low enough that migration engineering exceeds savings * Ecosystem familiarity is a material constraint * Your users recognize and expect the map ## Choose HERE when * You route commercial vehicles outside the contiguous United States * Your jurisdiction regulates by axle group * You carry ADR-regulated or radioactive cargo * You need toll cost for the described vehicle before dispatch * Your driver application must work offline * Compliance requires defensible route reconstruction * You need fleet-wide multi-stop optimization under constraints * You need transport restrictions as renderable map data, not only as routing behaviour * Asset-based pricing fits your workload better than per-call ## Choose both when * You have a consumer surface and an operational backend * Google for place search and consumer autocomplete; HERE for constrained routing, matrix operations, and batch geocoding * Your fleet is US-domestic today but your expansion plan is not — build the facade from HERE's constraint set now, whichever engine you call first Document the split as a decision, in writing, before someone frames it as a failed migration. ## Related documentation Google Large Vehicle Routing versus HERE truck routing, constraint by constraint. Confidence scoring, batch architecture, and how to benchmark. Why the toll figure depends on the vehicle profile, and what Google returns instead. Dual-running, shadow comparison, rollback. Also: [Migrating from Google Maps](/guides/google-migration) · [Reducing Google Maps Costs](/use-cases/reducing-google-maps-costs) · [Choosing the Right HERE APIs](/getting-started/choosing-the-right-here-apis) ## Sources **HERE** * [HERE Technologies documentation](https://www.here.com/docs) * [Routing API v8](https://www.here.com/docs/category/routing-api-v8) * [Geocoding & Search v7](https://docs.here.com/geocoding-and-search/docs/introduction-to-here-geocoding-search-api-v7) * [Tour Planning](https://docs.here.com/tour-planning/docs/introduction-tour-planning) **Google** * [Maps Platform documentation](https://developers.google.com/maps/documentation) * [Large vehicle routing overview](https://developers.google.com/maps/documentation/routes/lvr) — vehicle parameters, coverage, documented limitations * [Maps Platform pricing](https://developers.google.com/maps/billing-and-pricing/pricing) **Placematic** * [Commercial comparison and pricing overview](https://placematic.com/compare/) *HERE capabilities verified July 2026. Google Large Vehicle Routing capabilities, coverage and documented limitations verified August 2026. Capabilities and pricing change; verify against primary sources.* *** Need to compare these platforms with your own request mix? Placematic can help you run a technical and cost evaluation using representative routes, addresses and production volumes. Placematic is an official HERE Technologies reseller and implementation partner. [Cost Reduction Audit](https://placematic.com/here-location-services/cost-reduction-audit/). # HERE vs Mapbox Source: https://docs.placematic.com/comparisons/here-vs-mapbox Rendering and styling versus commercial vehicle routing. This is usually a hybrid, not a choice — and forcing an all-or-nothing migration is the mistake. # HERE vs Mapbox **This is rarely a platform decision. It is a layer decision.** Mapbox is the stronger rendering and styling platform. HERE is the stronger commercial vehicle routing platform. Teams that treat these as competing choices end up compromising the layer they care less about, for no reason. **Use Mapbox GL for the map. Use HERE for the routing.** That is a coherent architecture, it is common, and it should be stated as a decision rather than discovered as a compromise. ## Short verdict **Choose Mapbox when** the map's visual identity is part of your product, when your team's differentiator is the interface, or when your routing needs are passenger-vehicle only. **Choose HERE when** vehicle constraints must shape the route, when you need fleet optimization or map matching, or when transport-attributed data is the requirement. **Use both when** you need a beautiful map showing a truck-legal route. Which is most fleet software. ## Comparison scope Rendering, styling, mobile SDKs, navigation, routing, geocoding, offline operation, and commercial model. Both vendors' capabilities and pricing change. Verify against [Mapbox documentation](https://docs.mapbox.com/) and [HERE documentation](https://www.here.com/docs) before architecting. Nothing on this page substitutes for reading the current specification. ## Decision summary | Requirement | Better fit | Why | | ---------------------------------- | -------------------- | ------------------------------------------------------------- | | Custom map styling depth | Mapbox | Studio, style specification, design workflow | | Vector tile ecosystem | Mapbox | MapLibre GL forked from Mapbox GL JS; the ecosystem is theirs | | Developer experience for rendering | Mapbox | Widely regarded as the reference | | Commercial vehicle constraints | HERE | Height, weight per axle group, hazmat cargo, tunnel category | | Hazardous cargo routing | HERE | Native parameters | | GPS trace → road segments | HERE | Route Matching | | Fleet-wide multi-stop optimization | HERE | Tour Planning solves the VRP | | Transport-attributed base data | HERE | Weight and height restrictions in the map | | Passenger-car routing | Both credible | Test on your corridors | | Offline mobile maps | Both offer it | Compare current terms and binary size | | Geocoding | Test it | Neither is obviously superior for all regions | | Commercial model | Differs structurally | See below | ## Where Mapbox is stronger ### Styling and design workflow Mapbox Studio and the Mapbox Style Specification are the reference implementation for vector map styling. If your product's map is a designed artifact rather than a functional one — a real estate platform, a travel product, a data visualization — this is a genuine advantage that no routing capability offsets. ### The vector tile ecosystem MapLibre GL, the open-source library much of the industry now uses, forked from Mapbox GL JS. The tooling, the style spec, the mental model, and the community are Mapbox's, even where the code has diverged. Engineers who have built maps have probably built them with this stack. ### Developer experience Documentation, examples, and API ergonomics are consistently well regarded. This is not a small thing; it appears in your engineering budget. ## Where HERE is stronger ### Commercial vehicle routing HERE Routing v8 accepts vehicle height, width, length, gross weight, current weight, weight per axle, weight per axle *group* (single, tandem, triple, quad, quint), axle count, trailer count, ADR tunnel category, hazardous cargo types, and KPRA length. Verified against the [HERE Matrix Routing v8 OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi), v8.47.0, July 2026. Mapbox's Directions API supports a driving profile with some restriction handling. **Verify its current commercial vehicle constraint set directly** against [Mapbox's documentation](https://docs.mapbox.com/api/navigation/directions/) — we will not characterize a competitor's capability from a comparison table, including ours. The architecturally relevant question is: *can the API express every constraint that determines whether your vehicle may legally and physically traverse a segment?* Test this with the trap geometry below, not with a feature list. ### Route Matching Taking a noisy GPS trace and returning the road segments actually travelled, with attributes. This is what IFTA jurisdiction miles and speed compliance require. ### Tour Planning Capacitated VRP with time windows, multi-depot, heterogeneous fleets, priorities, pickup-and-delivery. HERE documents it as included in the Base Plan. ### Transport-attributed map data Weight restrictions, height limits, hazmat prohibitions, and designated truck routes present in the base map. ## Where the difference is mostly commercial or operational ### Commercial model The models differ structurally, not just in price. Mapbox has historically priced map loads and navigation differently from per-request routing. HERE prices per transaction, with an asset-based option in some tiers. **Do not compare these on rate cards.** A map-load-based model and a request-based model are not commensurable without knowing your session behaviour. Model your actual usage against both. Verify current pricing: [Mapbox pricing](https://www.mapbox.com/pricing) · [Placematic HERE pricing](https://placematic.com/here-location-services/here-pricing/). ### Data freshness Both maintain their own map data with their own update cadences. Mapbox incorporates OpenStreetMap alongside other sources; HERE maintains a proprietary map. **Neither claim about freshness should be accepted from a vendor.** Test the specific geography you operate in, particularly for recently built roads. ### Support and accountability Both offer enterprise support. HERE licensed through a Gold Partner means one contract and one accountable party for the integration, not merely for API availability. ## Architecture: the hybrid Say this out loud in a design review, and stop treating it as a compromise. ```mermaid theme={null} flowchart TB subgraph Client["Your application"] UI[Map UI] LOGIC[Business logic] end subgraph Render["Rendering — Mapbox or MapLibre"] GL[Mapbox GL JS / MapLibre GL] STYLE[Custom style] TILES[Vector tiles] end subgraph Route["Routing — HERE"] TR[Truck Routing
with constraints] MX[Matrix Routing] TP[Tour Planning] RM[Route Matching] end subgraph Yours["Yours regardless"] PG[(PostGIS:
zones, geocode cache,
geofences)] end UI --> GL --> STYLE GL --> TILES LOGIC --> TR LOGIC --> MX LOGIC --> TP LOGIC --> RM TR -->|polyline| GL LOGIC --> PG ``` **The interface is a polyline.** HERE returns route geometry; MapLibre GL renders it. There is no coupling beyond that, and no reason the same vendor must supply both. If you already render with MapLibre GL, adding HERE routing costs you a polyline decode and a fetch. It is not a migration. It is an addition. **Where the hybrid gets complicated:** **Mobile turn-by-turn.** If you need on-device navigation with offline maps, you are choosing an SDK, and the SDK bundles rendering with routing. This is the one place the hybrid breaks down, and you should decide it deliberately rather than discover it during the mobile sprint. **Attribution.** Both platforms require it. Both sets apply if you use both. **Two contracts, two invoices, two support paths.** Real overhead. Weigh it honestly. ## Migration considerations **Rendering: Mapbox GL JS → MapLibre GL is trivial.** Same lineage, largely compatible. **Rendering: Mapbox → HERE tiles.** If you use Mapbox GL JS for the library and Mapbox tiles for the data, swapping tiles is a source configuration change. Swapping the library is not needed. Styling will differ visibly. **Routing: Mapbox Directions → HERE Routing v8.** | Difference | Detail | | -------------------- | ---------------------------------------------------------------------------------------------- | | Coordinate order | Mapbox Directions uses `lng,lat` in the path. HERE uses `origin=lat,lng`. GeoJSON is `lng,lat` | | Response shape | Different geometry encodings and section structures | | `200` is not success | HERE returns `200` with empty `routes` and a `notice` when no path exists | | `403` ≠ `401` | `403` means valid credential, missing entitlement. Never retry | | Matrix semantics | HERE returns flat row-major arrays. Index carefully | | Vehicle profile | HERE's constraint set is richer. Design your abstraction around it | **Design any provider facade from HERE's capability set.** An interface derived from a routing API that cannot express axle-group weight has no field for it. When you add HERE, the constraint has nowhere to live and gets silently dropped. **Dual-run before you commit.** See [Google Migration Architecture](/architecture/google-migration-architecture) — the methodology applies regardless of which vendor you leave. ## Cost model **What creates billable activity:** * Map loads or tile requests (model differs by vendor) * One routing call per computed path * Matrix calls, or the routing loops that should have been matrix calls * Geocoding, real-time and batch * Rendering without a CDN **Request multiplication risks** are identical on both platforms: routing loops that should be matrices, reverse-geocoding GPS packets, undebounced autocomplete. **Hybrid cost note.** Running both means two minimums, two free tiers that do not pool, and two contracts. For a small team this overhead may exceed the benefit. For a fleet platform where the map is customer-facing and the routing is safety-critical, it does not. **Total cost of ownership** includes engineering time to approximate a capability your platform does not have. Implementing truck restriction logic over an unconstrained routing API is paying salaries to badly reproduce a product feature. ## How to evaluate with your own data **Rendering.** Build the same map on both, with your data, at your zoom levels. Show your designers. This is a subjective judgment and it is legitimate; make it explicitly rather than through a feature matrix. **Routing quality.** 500 real historical trips through both. Compare against telematics ground truth — actual driven distance and duration — not against each other. Report residual **distributions**, not means. **Truck constraint gate.** Route a vehicle with 410 cm height through: * The 11foot8 bridge, Durham NC * Storrow Drive, Boston MA * The Southern State Parkway, Long Island NY Any path returned is a failure. Run the same three in car mode as a control — all three must route. This is a gate, not a metric. It answers "can this API express my constraint" more reliably than any documentation review. **Offline.** Download a region on both mobile SDKs. Measure binary size impact and storage. Test on the oldest device your drivers actually use. **Cost.** Model your actual session and request behaviour against both pricing structures. Map loads and per-request billing are not commensurable in the abstract. ## Common decision mistakes **Treating it as all-or-nothing.** The most common and most costly. **Choosing HERE for routing and then abandoning Mapbox styling** because someone wanted vendor consolidation. **Choosing Mapbox for the map and then approximating truck constraints in application code.** You cannot; the road attributes are not exposed. **Comparing map-load pricing to per-request pricing** without modelling session behaviour. **Accepting either vendor's data freshness claim.** Test your geography. **Designing the abstraction layer around the weaker constraint set.** **Discovering the mobile SDK bundles rendering and routing** during the mobile sprint. **Forgetting attribution.** Both require it. **Failing over constrained truck routing to an unconstrained provider.** Worse than failing. ## Choose Mapbox when * The map is a designed artifact and part of your brand * Your routing needs are passenger-vehicle only * Your team's differentiator is the interface * Styling depth is a product requirement, not a preference ## Choose HERE when * Vehicle constraints determine route legality or physical feasibility * You transport hazardous materials * You need map matching for compliance * You need fleet-wide multi-stop optimization * Transport-attributed base data is a requirement ## Use both when * You need a beautiful map showing a truck-legal route Which describes most fleet software with a customer-facing surface. Write it in the architecture decision record. ## Related documentation Tiles, CDN caching, and the web-versus-mobile entitlement boundary. The constraint set, and the trap geometry that tests it. Where the hybrid breaks down: offline and on-device navigation. The architecture a hybrid usually sits inside. Also: [Route Matching](/guides/route-matching) · [Tour Planning](/guides/tour-planning) · [HERE vs Google Maps](/comparisons/here-vs-google-maps) · [HERE vs OpenStreetMap](/comparisons/here-vs-openstreetmap) ## Sources **HERE** * [Routing API v8](https://www.here.com/docs/category/routing-api-v8) * [Matrix Routing v8 OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi) * [HERE Map Rendering](https://www.here.com/docs/category/here-map-rendering) **Mapbox** * [Mapbox documentation](https://docs.mapbox.com/) * [Mapbox Directions API](https://docs.mapbox.com/api/navigation/directions/) * [Mapbox pricing](https://www.mapbox.com/pricing) **Placematic** * [Commercial comparison overview](https://placematic.com/compare/here-vs-mapbox/) *Verified July 2026. Mapbox capabilities characterized only where verifiable from their public documentation. Verify before architecting.* *** Need to compare these platforms with your own request mix? Placematic can help you run a technical and cost evaluation using representative routes, addresses and production volumes. Placematic is an official HERE Technologies reseller and implementation partner. [Cost Reduction Audit](https://placematic.com/here-location-services/cost-reduction-audit/). # HERE vs OpenStreetMap Source: https://docs.placematic.com/comparisons/here-vs-openstreetmap Not a product comparison. A managed platform versus a stack of open-source components you assemble, host, monitor and keep current. # HERE vs OpenStreetMap **OpenStreetMap is not a competitor to HERE. It is not a company, a product, or an API.** It is a geographic database, built by a volunteer community, released under the Open Database License. It has no routing engine, no geocoder, no tile server, no SLA, and nobody to call. If you are evaluating "HERE vs OpenStreetMap," you are actually evaluating **a managed platform versus a stack you assemble and operate.** That distinction is the entire page. ## Comparison scope Getting the terminology right is not pedantry. It determines what you are signing up to build. | Component | What it is | Common implementation | | ---------------------------- | ----------------------------------- | ----------------------------------------- | | **OpenStreetMap** | Geographic data + community project | The data itself. ODbL licensed. | | **Routing engine** | Computes paths over the data | OSRM, Valhalla, GraphHopper | | **Geocoder** | Address ↔ coordinate | Nominatim, Pelias, Photon | | **Tile server** | Renders map imagery | Tileserver GL, Tegola, Martin | | **Map matching** | GPS trace → road segments | Valhalla Meili, OSRM `/match` | | **Isochrones** | Reachability polygons | Valhalla, custom | | **Commercial OSM providers** | Managed OSM stacks | Geoapify, Stadia Maps, Mapbox (partially) | "Self-hosting OpenStreetMap" means selecting one component from most of those rows, integrating them, hosting them, monitoring them, and re-importing continental-scale data on a cadence you choose and maintain. It is a real and defensible choice. It is not a free version of a mapping API. This page compares **HERE Location Services** to **a self-hosted OSM-based stack**. Commercial OSM providers are a third option and are addressed at the end. ## Short verdict **Choose a self-hosted OSM stack when** location is your core competency, you have GIS engineering capacity, your operating region is bounded, your requirements are stable, and per-call cost at your volume genuinely exceeds the fully-loaded cost of the engineers who will operate it. **Choose HERE when** location is infrastructure rather than product, you need commercial vehicle attributes you cannot curate yourself, you need someone accountable when a route is wrong, or you cannot afford a data-freshness problem to become your problem. **Choose a hybrid when** you can identify a bounded, high-volume, low-stakes workload — and most teams can. ## Decision summary | Requirement | Better fit | Why | | ----------------------------------------------------------- | ---------------------------- | --------------------------------------------------------------------------------------------- | | Truck attributes: height, weight, axle, hazmat restrictions | HERE | OSM truck restriction tagging is inconsistent and incomplete; curating it is a data programme | | Real-time traffic | HERE | OSM has no traffic data. You would source and fuse it yourself | | Map data freshness at scale | HERE | OSM data is current; *your import* is as current as your pipeline | | Full control over routing profiles | OSM stack | You can express anything. That is the point | | Per-call marginal cost at very high volume | OSM stack | Approaches zero. Total cost does not | | Data residency and full sovereignty | OSM stack | Runs where you put it | | Bounded region, high volume geocoding | OSM stack | Nominatim over a national extract is genuinely good and free | | Accountability when a route is wrong | HERE | There is a contract and a person | | Rural / emerging-market road coverage | Test it | OSM coverage varies enormously by region and community activity | | Urban geometry in well-mapped cities | Comparable | OSM is often excellent. Test it | | Compliance-grade route reconstruction | HERE, or careful engineering | You must be able to cite the map version that produced a match | ## Where HERE is stronger ### Truck attribution This is the strongest argument and it is a data argument, not a software argument. Commercial vehicle routing needs bridge clearances, weight limits per axle group, hazmat prohibitions, tunnel categories, and designated truck routes — attributed consistently across a road network. OSM contains some of this. Coverage is uneven by country, by region, and by how active the local mapping community is. `maxheight` and `maxweight` tags exist; whether they exist on the bridge your driver is approaching is a different question. An OSRM instance will happily route a truck under a bridge whose clearance nobody tagged. It will return `200`. There is no vendor to escalate to, and the gap is not in your code. Curating truck attributes for a national network is a multi-year data programme, not a sprint. Verify this yourself for your operating region rather than accepting it: pick twenty bridges you know are low, and check whether they carry `maxheight` in the current OSM extract. ### Traffic OSM has no traffic layer. There is no historical speed profile, no live incident feed, no congestion model. You would need to source traffic separately — a commercial feed, or your own fleet's telematics — and fuse it into your routing engine's cost model. That is a legitimate project. It is a project. ### Data freshness as someone else's problem OSM data is edited continuously and is often *more* current than commercial data for recently changed geometry, particularly in actively mapped areas. **Your import is not.** A self-hosted stack is as fresh as your extract-transform-load pipeline. Continental-scale OSRM graph preprocessing is measured in hours and substantial memory. Weekly is achievable. Daily is work. For compliance workloads — IFTA jurisdiction miles, speed compliance against posted limits — you must be able to state which map version produced a given result, months later. That is an artifact you now version and retain. ### Accountability When HERE returns a wrong route, there is a support path and a contract. When your OSRM instance returns a wrong route, the answer is a task in your backlog. Whether that matters depends on whether a wrong route is an inconvenience or a bridge strike. ## Where the OSM stack is stronger ### Total control You can express routing profiles no vendor offers. Custom cost functions, bespoke restrictions, domain-specific penalties. Valhalla in particular is designed for this. If your routing problem is genuinely unusual — mining haul roads, agricultural equipment, restricted campus networks — no commercial API will express it and an OSM stack will. ### Marginal cost approaches zero At very high call volume, per-request pricing dominates. A self-hosted OSRM instance serving a bounded region has a fixed infrastructure cost and no marginal cost per route. The threshold is real but it is higher than teams expect, because the comparison is not "server cost versus API cost." It is "server cost + engineering salaries + on-call + data pipeline + monitoring + the opportunity cost of what those engineers were not building" versus API cost. Compute your own crossover. Include the salaries. ### Data sovereignty The stack runs where you put it. No coordinates leave your infrastructure. For some regulated industries and some jurisdictions this is dispositive and no commercial platform can satisfy it. ### Geocoding in a bounded region Nominatim over a national extract, with a well-tuned normalization pipeline, is a genuinely strong geocoder for a bounded operating area. It costs nothing per call. This is the single most defensible self-hosted component and the easiest to start with. A hybrid that works: Nominatim for the geocoding backfill, falling through to a paid geocoder only for records Nominatim cannot resolve confidently. You pay for the hard 15% and get the easy 85% free. See [High-Volume Geocoding](/architecture/high-volume-geocoding). ## Where the difference is operational, not technical ### Total cost of ownership The naive comparison — server cost versus invoice — is wrong. The honest inventory: | Cost | Self-hosted OSM | HERE | | --------------------------- | ------------------------- | ---------------- | | Per-call | \~0 | Per contract | | Infrastructure | Yours | — | | Data import pipeline | Yours to build and run | — | | Graph preprocessing compute | Yours | — | | Monitoring and alerting | Yours | Partly | | High availability | Yours to design | Contractual | | On-call | Yours | — | | Engineering to maintain | 1 to several FTE, ongoing | Integration only | | Truck attribute curation | Yours, if needed | Included | | Traffic data | Sourced separately | Included | | Map version provenance | Yours to retain | Documented | | Accountability when wrong | Yours | Contractual | **The engineering line is the one that surprises people.** It is not a one-time build. Someone must own the import pipeline, the graph rebuild, the version tracking, and the pager, indefinitely. For a TMS or ELD vendor, a self-hosted routing stack becomes a second product that no customer pays for and no product manager owns. This is the most common way the decision goes wrong: it is correct at the moment it is made and becomes wrong as the team's priorities move. ### Licensing and attribution **OpenStreetMap data is licensed under the [Open Database License (ODbL)](https://opendatacommons.org/licenses/odbl/).** This is not a footnote. The ODbL has share-alike provisions. If you produce a **Derivative Database** — a modified version of OSM data — and publicly use it, you must offer that derivative under the ODbL. Producing a **Produced Work** (a map image, a route result) requires attribution but does not trigger share-alike. Where exactly a given system sits on that boundary is a legal question with genuinely contested edges — particularly for systems that merge OSM with proprietary data, or that expose the underlying geometry to end users. If you are a commercial platform embedding OSM-derived routing into a product, get counsel. Do not resolve the Derivative Database question with a blog post. The [OSMF Community Guidelines](https://osmfoundation.org/wiki/Licence/Community_Guidelines) exist because the boundary is unclear. Attribution is required in all cases. "© OpenStreetMap contributors" is not decorative. HERE data is commercially licensed with terms your legal team can read once and file. This is a real advantage and it is rarely mentioned. ## Architecture implications ```mermaid theme={null} flowchart TB subgraph OSM["Self-hosted OSM stack — you own every box"] EXTRACT[Regional extract
Geofabrik / planet] IMPORT[Import + preprocess
hours, high memory] OSRM[OSRM / Valhalla
routing] NOM[Nominatim
geocoding] TILE[Tile server] MON[Monitoring / HA / on-call] VER[(Map version registry
for compliance)] end subgraph HERE["HERE — you own the integration"] API[Routing / Matrix / Geocoding / Tiles] GW[Your gateway:
cache, quota, retry] end subgraph Yours["Identical either way"] PG[(PostGIS:
materialized zones,
geocode cache)] APP[Application] end EXTRACT --> IMPORT --> OSRM IMPORT --> NOM IMPORT --> TILE MON -.-> OSRM IMPORT --> VER OSRM --> APP GW --> API API --> GW --> APP PG --> APP ``` Notice the bottom box. **Caching, materialized delivery zones, and the geocode cache are yours regardless of platform.** A large fraction of what most location systems do never touches either. That has a consequence for this decision: if you have not yet materialized your isolines and cached your geocodes, your API bill is not evidence that you need to self-host. It is evidence that you have not optimized. See [Cost Optimization Patterns](/architecture/cost-optimization-patterns). **Monitoring differs fundamentally.** With HERE you monitor call ratios, `429` rates, and `403`s. With a self-hosted stack you additionally monitor graph rebuild success, import freshness, memory headroom, and query latency percentiles — and you are the escalation path. **Failover.** A commercial API has a documented availability commitment. Your OSRM instance has whatever you built. Multi-region self-hosted routing is achievable and is a project. ## Migration considerations Migrating *to* a self-hosted stack: * **Coordinate order.** OSRM uses `lng,lat`. HERE Routing uses `lat,lng`. GeoJSON is `lng,lat`. This will bite you. * **Duration semantics.** OSRM free-flow durations are not traffic-aware. Substituting them for traffic-aware ETAs will make every arrival estimate optimistic, systematically, and your customers will notice before your dashboards do. * **No matrix async.** OSRM's `/table` is synchronous with its own practical ceiling. * **Map matching.** Valhalla Meili and OSRM `/match` exist and are capable. Whether their output is defensible in an IFTA audit is a question you must answer, and part of the answer is retaining the map version. * **Truck profiles.** OSRM's default profiles do not model truck constraints meaningfully. Custom Lua profiles can, subject to the underlying data being tagged. **Dual-run before you commit.** Route your production trips through both. Compare against telematics ground truth, not against each other. See [Google Migration Architecture](/architecture/google-migration-architecture) — the methodology is identical regardless of which direction you migrate. ## How to evaluate with your own data Before deciding, answer these empirically for **your** region: **Data coverage.** Take twenty bridges, tunnels, or weight-restricted segments you know exist in your operating area. Check whether the current OSM extract carries the restriction tags. Report the fraction. **Routing quality.** Route 500 real historical trips through OSRM (or Valhalla) and through HERE. Compare both against telematics actuals. Report the residual *distribution*, not the mean. **Truck constraint gate.** Route a 4.1 m vehicle through the 11foot8 bridge (Durham NC), Storrow Drive (Boston), and the Southern State Parkway (Long Island). Any path returned is a failure. Run the same three in car mode as a control. **Geocoding.** Take 1,000 of your own addresses — deliberately including rural, apartment, and PO-box-adjacent cases. Compare Nominatim against a commercial geocoder for match rate *and* for confidence calibration. **Operational cost.** Build the import pipeline as a spike. Time it. Multiply by your rebuild cadence. Add the engineer. **Then compute the crossover.** Include salaries. Run the coverage test first. It takes an afternoon and it frequently ends the discussion for commercial vehicle operators. ## Common decision mistakes **Treating OSM as a vendor.** It is data and a community. **Comparing server cost to invoice.** Omits the engineers, the pipeline, and the pager. **Assuming OSM truck restrictions are complete.** Test your region. **Assuming OSM data is stale.** Often it is not. Your *import* might be. **Deciding on marginal cost at a volume you have not yet reached.** **Building a self-hosted stack before caching and materializing.** You may be self-hosting waste. **Ignoring ODbL share-alike.** Get counsel before you ship a derivative database. **Omitting attribution.** Required. **Building it because it is interesting.** It is interesting. That is not a business case. **Assuming free-flow durations substitute for traffic-aware ETAs.** **Not versioning the map that produced a compliance-relevant result.** ## Choose a self-hosted OSM stack when * Location is core to your product, not infrastructure beneath it * You have — and will keep — GIS engineering capacity * Your operating region is bounded and well mapped * Your routing requirements are unusual enough that no commercial API expresses them * Data sovereignty is a hard requirement * You have computed the crossover including salaries, and it favours self-hosting * Your workload is high-volume and low-stakes: geocoding backfills, analytical distance computation, internal tooling ## Choose HERE when * You route commercial vehicles and cannot curate truck attributes yourself * You need traffic * Location is infrastructure, and your engineers should be building your product * Compliance requires attributable, versioned map data with a party accountable for it * The fully-loaded cost of operating a stack exceeds the API bill * You want the licensing question to be a contract rather than a legal opinion ## A third option **Commercial OSM providers** — Geoapify, Stadia Maps, and others — sell managed OSM-based routing, geocoding, and tiles. You get OSM's data and licensing characteristics without operating the stack. This is frequently the right answer for teams attracted to OSM for cost reasons rather than control reasons. You inherit OSM's truck-attribute gaps and its lack of traffic, but not its operational burden. Evaluate them explicitly rather than treating the choice as binary. ## Related documentation Materialize and cache before you conclude you need to self-host. Where a self-hosted geocoder genuinely fits, as a first tier. The attributes an OSM stack must supply and usually cannot. Why the map version that produced a match is an audit artifact. Also: [Route Matching](/guides/route-matching) · [Geofencing](/use-cases/geofencing) · [HERE vs Google Maps](/comparisons/here-vs-google-maps) ## Sources **OpenStreetMap** * [OpenStreetMap](https://www.openstreetmap.org/) * [Open Database License (ODbL)](https://opendatacommons.org/licenses/odbl/) * [OSMF licence community guidelines](https://osmfoundation.org/wiki/Licence/Community_Guidelines) **Components** * [OSRM](http://project-osrm.org/) * [Valhalla](https://valhalla.github.io/valhalla/) * [Nominatim](https://nominatim.org/) **HERE** * [Routing API v8](https://www.here.com/docs/category/routing-api-v8) * [HERE Technologies documentation](https://www.here.com/docs) **Placematic** * [Commercial comparison overview](https://placematic.com/compare/here-vs-openstreetmap/) *Verified July 2026. Licensing interpretation is not legal advice.* *** Need to compare these platforms with your own request mix? Placematic can help you run a technical and cost evaluation using representative routes, addresses and production volumes. Placematic is an official HERE Technologies reseller and implementation partner. [Cost Reduction Audit](https://placematic.com/here-location-services/cost-reduction-audit/). # HERE vs TomTom Source: https://docs.placematic.com/comparisons/here-vs-tomtom Two credible commercial platforms with genuinely overlapping truck routing. Where the difference is real, where it is marketing, and the one gap that decides it for US carriers. # HERE vs TomTom This is the closest comparison in this section, and the one most often written badly. TomTom is not a rendering vendor that added routing. It is a mapping company with a commercial vehicle routing product, an ADR tunnel restriction implementation, hazmat load types, and a matrix API. Most published comparisons — including, until recently, Placematic's own — understate it. **The differences are narrower than either vendor's marketing suggests, and one of them is decisive.** ## Short verdict **Choose HERE when** you route in a jurisdiction that regulates by axle group, when you need a fleet-scale Vehicle Routing Problem solver, or when you need map matching for compliance reconstruction. **Choose TomTom when** you are already committed to their automotive stack, when European navigation and traffic are your primary concern, or when their commercial terms are materially better for your workload — which they may be. **Where the difference is insignificant:** basic truck constraint expression, ADR tunnel codes, hazmat load types, reachable-range isochrones, and passenger-car routing quality. Both are credible. Test on your corridors. ## Comparison scope Routing, truck routing, matrices, optimization, traffic, geocoding, SDKs, and enterprise deployment. TomTom operates two map generations: the legacy TomTom Maps and **TomTom Orbis Maps**, their next-generation offering. Capability differs between them. TomTom's own Android SDK documentation states, for `Vehicle.Truck`: *"This option is not supported with the Orbis map."* Verify which map your intended API version and SDK target, and whether truck routing is available on it, before you architect. Source: [TomTom Android SDK — Vehicle.Truck](https://developer.tomtom.com/assets/downloads/tomtom-sdks/android/api-reference/1.24.2/vehicle/model/com.tomtom.sdk.vehicle/-vehicle/-truck/index.html), v1.24.2, retrieved July 2026. ## Decision summary | Requirement | Better fit | Why | | ---------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------ | | Per-axle-group weight expression | **HERE** | TomTom exposes a single `vehicleAxleWeight` scalar | | Fleet-scale VRP: capacity, time windows, multi-depot | **HERE** | Tour Planning. TomTom's Waypoint Optimization orders one vehicle's stops | | GPS trace → road segments for compliance | **HERE** | Route Matching. Verify any TomTom equivalent independently | | Basic truck dimensions and weight | Comparable | Both express height, width, length, weight, axle count | | ADR tunnel restriction codes | Comparable | Both implement `B`, `C`, `D`, `E` | | Hazmat load types | Comparable | Different taxonomies, similar intent | | Truck-constrained matrix | Comparable | Both support it; compare mode-specific ceilings, not headline figures | | Reachable range / isochrone with truck constraints | Comparable | Both offer it | | EV consumption modelling | TomTom is notably granular | Efficiency pairs, altitude gain/loss, auxiliary power | | Automotive / in-dash integration | TomTom | Their historical core market | | European navigation heritage | TomTom | Genuine, and hard to quantify | | Truck support on the newest map generation | **Verify** | Orbis limitation above | ## The decisive difference: axle groups This is the single most important paragraph on this page. **TomTom** exposes `vehicleAxleWeight` — *weight per axle, in kilograms* — as a scalar, alongside `vehicleNumberOfAxles`. Source: [TomTom Calculate Route documentation](https://developer.tomtom.com/routing-api/documentation/tomtom-maps/calculate-route). **HERE** exposes `weightPerAxle` as a scalar *and* `weightPerAxleGroup`, with distinct values for `single`, `tandem`, `triple`, `quad`, and `quint` groups. Source: [HERE Matrix Routing v8 OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi), v8.47.0. **This is not a minor parameter difference. It is a legality difference in the United States.** US federal bridge formula and state weight limits are frequently expressed per axle *group*, not per axle. A five-axle tractor-trailer with a steer axle, a tandem drive, and a tandem trailer group cannot be described by a single per-axle scalar without losing the information the regulation is written against. If your operating jurisdiction regulates by axle group and your routing API cannot express it, the API cannot determine whether a route is legal for your vehicle. No amount of other truck parameters compensates. For European operations where restrictions are more often expressed by gross weight and per-axle limits, the gap narrows substantially. **This is a market-specific decision.** Verify TomTom's current capability directly. It may have changed. ## Where HERE is stronger ### Axle group expression Above. ### Fleet-scale optimization HERE Tour Planning solves the Vehicle Routing Problem: capacitated VRP, time windows, multi-depot, heterogeneous fleets, priorities, pickup-and-delivery, reloads. HERE documents it as included in the Base Plan. TomTom's **Waypoint Optimization** API orders waypoints for a single vehicle, with `waypointConstraints` (origin, destination, visiting orders) and `groupConstraints`. Source: [TomTom Waypoint Optimization](https://developer.tomtom.com/waypoint-optimization/documentation/waypoint-optimization). These are different problems. Waypoint optimization sequences one vehicle's stops. A VRP solver *assigns* stops across a fleet under capacity, shift, and skill constraints, and tells you which jobs it could not serve. If your problem is "which of my twelve technicians visits which of my sixty jobs, in what order, respecting appointment windows and certifications," waypoint optimization does not solve it. ### Route matching HERE offers map matching: a noisy GPS trace in, the road segments actually travelled out, with attributes. This is what IFTA jurisdiction-mile reporting and speed compliance against posted limits require. **Verify whether TomTom currently offers an equivalent product.** We will not assert its absence from memory. ### Hazmat cargo type granularity HERE's `shippedHazardousGoods` accepts an array from: `explosive`, `gas`, `flammable`, `combustible`, `organic`, `poison`, `radioactive`, `corrosive`, `poisonousInhalation`, `harmfulToWater`, `other`. TomTom's `vehicleLoadType` accepts values including `otherHazmatExplosive`, `otherHazmatGeneral`, and US hazmat classes. Both are workable. Map your material classifications deliberately against whichever you choose, and have the mapping reviewed by someone qualified in the applicable regulation. ## Where TomTom is stronger ### EV consumption modelling granularity TomTom's routing parameters express a consumption model in unusual detail: `constantSpeedConsumptionInkWhPerHundredkm` as speed/consumption pairs, `accelerationEfficiency` and `decelerationEfficiency`, `uphillEfficiency` and `downhillEfficiency`, `consumptionInkWhPerkmAltitudeGain`, `recuperationInkWhPerkmAltitudeLoss`, `auxiliaryPowerInkW`, `currentChargeInkWh`, `maxChargeInkWh`. Source: [TomTom common routing parameters](https://developer.tomtom.com/routing-api/documentation/tomtom-maps/common-routing-parameters). If your EV feasibility modelling is sensitive to gradient and regenerative braking — mountainous corridors, heavy vehicles — this parameter set is worth evaluating on its own terms. It expresses a physical model rather than a lookup. See [Fleet Electrification](/use-cases/fleet-electrification). ### Automotive and in-dash heritage TomTom's core market is automotive OEM integration. If your product ships into a vehicle head unit, or if you need navigation SDK maturity in that context, this is a real advantage that no API comparison captures. ### European navigation TomTom's European navigation heritage is genuine. Whether it produces better routes on your specific corridors at your specific departure times is an empirical question, and we will not answer it from a marketing page. ## Where the difference is commercial or operational ### Traffic Both vendors claim traffic superiority. Both have credible sources. **Do not accept a traffic quality claim from any vendor, including us.** Traffic quality is geographically uneven, temporally variable, and measured differently by whoever is measuring. A platform that is better in Amsterdam may be worse in Atlanta. Test it: 500 real historical trips, compared against telematics ground truth, on your corridors, at your departure times. Report the residual distribution — median, p90, p99 — not the mean. A platform whose durations are unbiased on average but have twice the variance produces twice as many late deliveries. ### Matrix size limits Matrix ceilings are often compared as single headline figures. That comparison is meaningless without stating the mode. HERE's limits, from the [Matrix Routing v8 OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi) (v8.47.0, July 2026): | Mode | Sync | Async | Live traffic + custom options | | --------------------------- | ----------------------- | --------------- | ----------------------------- | | Flexible (unlimited region) | 15×100 or 100×1 | 15×100 or 100×1 | Yes | | Region (≤400 km diameter) | 500×500 | 10,000×10,000 | Yes | | Profile (unlimited region) | 500×500, 1×2000, 2000×1 | 10,000×10,000 | No | A truck-constrained matrix with live traffic runs in **Flexible** mode and caps at **15 × 100**. TomTom's Matrix Routing v2 imposes its own limits, which differ between the synchronous and asynchronous endpoints. **Verify them directly** at [TomTom's Matrix Routing documentation](https://developer.tomtom.com/matrix-routing-v2-api/documentation/synchronous-matrix). Comparing headline matrix figures across vendors, without stating mode and traffic-awareness, produces a comparison that is technically true and operationally useless. ### Pricing We will not publish a comparative price or a savings percentage. Competitor rate cards change without notice. **Verify current TomTom rates** at [TomTom pricing](https://developer.tomtom.com/store/maps-api) directly, and current HERE rates through your account manager. Cost outcome depends on API mix, monthly volume, region, contract terms, billing SKU, batching, and architecture. Those span more than an order of magnitude. Structurally: both bill per transaction. HERE offers an asset-based commercial model in some tiers, priced per tracked vehicle rather than per call. Availability depends on contract tier. A dispatcher who reroutes 200 trucks forty times a day is punished by call-volume pricing for operational diligence. If your vehicle population is countable and your call volume is not, ask both vendors about asset-based or fleet-based commercial terms before comparing rate cards. ### Support and deployment Both offer enterprise support. HERE licensed through a Gold Partner means one contract, one invoice, and one accountable party for the integration — not merely for the API being up. Whether that matters depends on whether a wrong route is an inconvenience or a bridge strike. ## Technical comparison ### Parameter semantics that differ | Concept | HERE | TomTom | | --------------- | -------------------------------------------- | ----------------------------------------------------- | | Height | `height`, centimetres | `vehicleHeight`, **metres** | | Weight | `grossWeight`, kilograms | `vehicleWeight`, kilograms | | Per-axle weight | `weightPerAxle` **and** `weightPerAxleGroup` | `vehicleAxleWeight` (scalar) | | Axle count | `axleCount` (2–255) | `vehicleNumberOfAxles` | | ADR tunnel | `tunnelCategory`: `B`\|`C`\|`D`\|`E` | `vehicleAdrTunnelRestrictionCode`: `B`\|`C`\|`D`\|`E` | | Hazmat cargo | `shippedHazardousGoods` (11 values) | `vehicleLoadType` (array) | | Commercial flag | `category: lightTruck` (exemption) | `vehicleCommercial: boolean` | **Units differ.** HERE's `height` is centimetres. TomTom's `vehicleHeight` is metres. Pass `4.1` to HERE and you have declared a four-centimetre vehicle. It will route anywhere. The response will be `200`. This is the single most likely migration defect between these two platforms, in either direction. ### Response semantics **HERE returns `200` with an empty `routes` array** and a `notice` containing `routeCalculationFailed` when no path exists. Checking `resp.ok` swallows the failure. For truck and hazmat routing, "no legal route" is a real and correct outcome. **HERE matrix results are flat, row-major arrays.** `travelTimes: [73, 1231, 983, 400]` for a 2×2. TomTom's synchronous matrix returns objects with explicit `originIndex` and `destinationIndex`. TomTom's shape is harder to get wrong. HERE's flat array produces a **silently transposed matrix** if indexed incorrectly: every travel time is plausible, every assignment is wrong, and nothing throws. Write one indexing helper, unit-test it against a hand-computed 2×3, and never index the array anywhere else. **HERE returns per-cell `errorCodes`** in matrix responses, including `3` — route found but violates a restriction. Read them. **`403` is not `401`** on HERE. `403` means a valid credential lacking entitlement for that product. Never retry it. ## Architecture implications The platform choice changes less than expected, because most of what a location system does should not touch a location API. ```mermaid theme={null} flowchart TB subgraph Yours["Identical on either platform"] PG[(PostGIS:
materialized zones,
geocode cache,
geofence containment)] GW[Gateway:
cache, quota,
retry, cost attribution] end subgraph Common["Both platforms"] RT[Truck routing
dimensions, ADR, hazmat] MX[Truck-constrained matrix] ISO[Reachable range] end subgraph HereOnly["HERE only"] AXLE[Weight per axle group] TP[Tour Planning VRP] RM[Route Matching] end GW --> PG GW -->|cache miss| RT GW --> MX GW --> ISO GW --> AXLE GW --> TP GW --> RM ``` **Materialized delivery zones, cached geocodes, and geofence containment are yours regardless.** If you have not built them, your API bill is not evidence you need a different vendor. See [Cost Optimization Patterns](/architecture/cost-optimization-patterns). **Vendor abstraction.** **Design your provider facade from the richer constraint set.** An interface with a single `axleWeight` scalar has no field for `weightPerAxleGroup`. When you add HERE, the constraint has nowhere to live and gets silently dropped. Convert units at each adapter, not in the interface. Store centimetres or store metres — pick one, and make the adapter responsible. **Failover between them is more plausible than with an unconstrained provider**, because both express most truck constraints. It is still dangerous: if you fail over from HERE with axle-group weight to TomTom without it, you have silently relaxed a legal constraint. Refuse to route rather than route with a degraded constraint set. ## Migration considerations **Moderate in both directions.** Neither is a rewrite of your spatial core, because your spatial core should be PostGIS. | Area | Effort | Notes | | ---------------- | --------------------------- | ------------------------------------------------------- | | Unit conversion | Low, high risk | Metres ↔ centimetres. Get this wrong once | | Coordinate order | Low | Verify per endpoint on both | | Response schema | Medium | HERE flat arrays vs TomTom indexed objects | | Error semantics | Medium | HERE's `200` with empty `routes`; per-cell `errorCodes` | | Hazmat taxonomy | Medium | Different value sets. Re-map, have it reviewed | | Axle group | **Blocking, one direction** | HERE → TomTom loses it. TomTom → HERE gains it | | Optimization | High, one direction | Waypoint Optimization ≠ Tour Planning | | Map matching | High, one direction | Verify TomTom equivalent | **Migrating from HERE to TomTom in a jurisdiction that regulates by axle group is not a migration.** It is shipping a system that cannot determine route legality. If this describes you, the comparison ends here. **Dual-run before you commit.** Shadow-write, compare offline against telematics ground truth, cut over one endpoint at a time, and test the rollback before you need it. See [Google Migration Architecture](/architecture/google-migration-architecture) — the methodology is vendor-agnostic. ## Cost model **What creates billable activity** — identical on both: * One routing call per computed path * Matrix calls, or the routing loops that should have been matrix calls * Reachable-range computations in a request path rather than materialized * Requesting turn-by-turn instructions when you consume a duration * Geocoding without a cache **Request multiplication risks:** the routing loop is the risk on both platforms. An n×m cost table is n·m routing calls or one matrix call. That is a complexity difference and no rate card closes it. See [Routing vs Matrix](/architecture/choosing-routing-vs-matrix). **Total cost of ownership** includes the engineering time to approximate a constraint your platform cannot express. A team implementing axle-group bridge formula logic on top of a scalar `vehicleAxleWeight` is paying salaries to badly reproduce a regulation. ## How to evaluate with your own data **First, answer the axle-group question.** Does your operating jurisdiction regulate by axle group? If yes, and TomTom still exposes only a scalar, the evaluation is over. This takes one phone call to your compliance officer. **Second, verify the Orbis truck limitation.** If you intend to target TomTom Orbis Maps, confirm truck routing availability on it for your specific API version and SDK. **Then, if both survive:** **Truck constraint gate.** Route a vehicle with a 4.1 m / 410 cm height through: * The 11foot8 bridge, Durham NC * Storrow Drive, Boston MA * The Southern State Parkway, Long Island NY Any path returned is a failure. Run the same three with a car profile as a control — all three must route, proving the test exercises the constraint rather than failing for an unrelated reason. **Watch the units.** Send `410` to HERE and `4.1` to TomTom. Sending `4.1` to HERE declares a four-centimetre truck and every trap location will route, and you will conclude — wrongly — that HERE has no constraint handling. **Routing and traffic quality.** 500 real historical trips through both, compared against **telematics ground truth**, not against each other. Two wrong answers can agree. Report the residual distribution, not the mean. Include peak and off-peak departures on the corridors you actually drive. **Matrix.** Build a matrix in the configuration your production system will use — truck constraints, live traffic, your real origin-destination counts. Verify it does not exceed the mode-specific ceiling on either platform. Do this before you architect around a headline number. **Optimization.** If you need fleet assignment, hand both platforms a real problem: twelve vehicles, sixty jobs, appointment windows, capacity, and two vehicles with a required capability. Compare what comes back, and compare how each reports jobs it could not assign. **EV, if relevant.** TomTom's consumption model expresses gradient and regenerative braking directly. If your corridors are mountainous, benchmark energy consumption against actuals, not just duration. **Cost.** Instrument your per-endpoint call counts from logs. Price the same counts on both, after applying caching and batching to both. Otherwise you are comparing your own inefficiency on two rate cards. ## Common decision mistakes **Assuming TomTom has no truck routing.** It does. Dimensions, weight, axle count, ADR tunnel codes, hazmat load types, and truck-constrained matrices. **Assuming the truck constraint sets are equivalent.** Axle groups. **Confusing Waypoint Optimization with a VRP solver.** **Comparing matrix ceilings without stating mode and traffic-awareness.** **Passing metres where centimetres are expected.** A four-centimetre truck routes anywhere, and returns `200`. **Accepting a traffic superiority claim from either vendor.** **Citing a matrix ceiling or a competitor price without a source.** Both change. Verify against the specification and the vendor's current pricing page. **Designing the abstraction layer from the weaker constraint set.** **Failing over from a platform that expresses a legal constraint to one that does not.** **Targeting Orbis Maps without verifying truck support.** **Comparing rate cards.** Call mix, tier, batching, and caching determine your bill. ## Choose HERE when * You operate in a jurisdiction that regulates by axle group * You need fleet-scale VRP: assignment across vehicles, under capacity, windows, and skills * Compliance requires defensible map matching and route reconstruction * You need truck routing on the vendor's current-generation map without qualification * Asset-based commercial terms fit your workload better than per-call ## Choose TomTom when * You are committed to their automotive or in-dash stack * European navigation and traffic are your primary market * Your EV consumption modelling needs gradient and regeneration granularity * Their commercial terms are materially better for your specific request mix — verify this, do not assume it either way * Your truck constraints are fully expressible with a per-axle scalar and gross weight ## Where the difference is insignificant Basic dimensional constraints. ADR tunnel codes. Reachable-range isochrones. Passenger-car routing quality. Geocoding for most Western markets. **Do not migrate for these.** The engineering cost exceeds the benefit and you will spend a quarter proving it. ## Related documentation HERE's constraint set, units, and the trap geometry that tests it. Cargo types, tunnel categories, and refusing to route. What a VRP solver does that waypoint optimization does not. Modes, ceilings, and the flat array that transposes silently. Also: [Route Matching](/guides/route-matching) · [Fleet Electrification](/use-cases/fleet-electrification) · [Fleet Routing](/use-cases/fleet-routing) · [HERE vs Google Maps](/comparisons/here-vs-google-maps) ## Sources **HERE** * [Matrix Routing v8 OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi) — authoritative on vehicle parameters and matrix modes * [Routing API v8 developer guide](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/get-started.html) * [Tour Planning introduction](https://docs.here.com/tour-planning/docs/introduction-tour-planning) **TomTom** * [Calculate Route — vehicle parameters](https://developer.tomtom.com/routing-api/documentation/tomtom-maps/calculate-route) * [Common routing parameters — consumption models](https://developer.tomtom.com/routing-api/documentation/tomtom-maps/common-routing-parameters) * [Matrix Routing v2 — synchronous](https://developer.tomtom.com/matrix-routing-v2-api/documentation/synchronous-matrix) * [Waypoint Optimization](https://developer.tomtom.com/waypoint-optimization/documentation/waypoint-optimization) * [Vehicle restrictions knowledge base](https://developer.tomtom.com/knowledgebase/apis/articles/enhancing-road-safety-tomtom-s-routing-api-vehicle-restrictions/) * [Android SDK — Vehicle.Truck](https://developer.tomtom.com/assets/downloads/tomtom-sdks/android/api-reference/1.24.2/vehicle/model/com.tomtom.sdk.vehicle/-vehicle/-truck/index.html) — Orbis limitation * [Routing API — Orbis Maps v2](https://developer.tomtom.com/routing-api/documentation/tomtom-orbis-maps/v2/product-information/introduction) **Placematic** * [Commercial comparison overview](https://placematic.com/compare/here-vs-tomtom/) *Verified July 2026 against HERE Matrix Routing v8.47.0 and TomTom developer documentation. TomTom's Orbis truck limitation cited from Android SDK v1.24.2. Capabilities, limits and pricing change; verify against primary sources before architecting.* *** Need to compare these platforms with your own request mix? Placematic can help you run a technical and cost evaluation using representative routes, addresses and production volumes. Placematic is an official HERE Technologies reseller and implementation partner. [Cost Reduction Audit](https://placematic.com/here-location-services/cost-reduction-audit/). # Comparisons Source: https://docs.placematic.com/comparisons/index Technical decision guides for teams evaluating HERE against Google Maps, Mapbox, TomTom, Azure Maps and self-hosted OSM stacks. # Comparisons There is no best mapping platform. There are platforms that fit particular architectures, particular request mixes, and particular commercial constraints. These pages exist to help you determine which one you are. ## How to use this section Each page answers a conditional question — *choose X when this is true, choose Y when that is true* — rather than declaring a winner. Where a difference is decisive we say so. Where it depends on your plan, region, or contract we say that instead. We work with HERE. Placematic is a HERE Gold Partner and reseller. That is a reason to read these pages carefully, not a reason to distrust them: a comparison that recommended HERE in every category would be useless to you and, ultimately, to us. These are technical decision guides. For commercial summaries, pricing overviews and lead-generation material, see the [Placematic comparison hub](https://placematic.com/compare/). The two serve different purposes and we do not duplicate one in the other. ## What actually differentiates location platforms Five axes. Most evaluations look at one. **Data.** Road network geometry, truck attributes, traffic, POI coverage, address precision. This is where platforms genuinely differ and where the difference is hardest to test from a documentation page. **API capability.** Not whether an endpoint exists but whether it expresses the constraint you need. A truck routing API that cannot express axle weight per axle group is not a truck routing API for a market that regulates by axle group. **Architecture fit.** Batch versus real-time. Synchronous versus job-based. Whether the platform's primitives match your workload shape. This determines your call volume, and call volume determines your bill far more than the rate card does. **Commercial model.** Per-call, per-session, per-asset, per-transaction. Free bundles, volume tiers, contract stability. What is entitled on your plan versus what is documented as available on some plan. **Support and ownership.** Whether you operate the stack or someone else does. Whether there is a human to call when a route is wrong. Rate cards are close across vendors at entry level. Your bill is determined by call mix, volume tier, whether you batched, and whether you cached — not by the headline per-1,000 price. Comparing rate cards teaches you very little. See [Cost Optimization Patterns](/architecture/cost-optimization-patterns). ## Why you must test with your own data Published accuracy figures are aggregate. Your addresses are not aggregate. A geocoder that resolves 97% of US urban addresses to rooftop may resolve 60% of your rural service-area addresses to a street centroid. A routing engine with excellent aggregate ETA accuracy may be systematically optimistic on the specific corridors your fleet drives at the specific hour it drives them. **Neither we nor any vendor can tell you which platform is more accurate for your data.** We can tell you how to find out. Every comparison page in this section ends with an evaluation plan: a representative route set, a representative address set, a production-like request mix, and a cost forecast built from your own call counts. Run it. The result will be more useful than any table on this site. ## The pages Platform-level fit. Consumer applications versus B2B operational systems. Commercial vehicle constraints, matrix semantics, and where Google is the better choice. Address precision, batch architecture, POI discovery, and how to benchmark on your own dataset. Rendering and styling versus commercial vehicle routing. Usually a hybrid, not a choice. Two credible commercial platforms. Where the difference is real and where it is marketing. A procurement and enterprise-architecture decision more than an API decision. Not a product comparison. Managed platform versus a self-hosted stack you assemble and operate. ## What we will not publish **Unverified savings percentages.** Cost outcomes depend on API mix, volume, region, contract, billing SKU, batching, and architecture. A single number claiming to represent all of those is not a claim, it is a slogan. **Plan-specific limits presented as universal.** HERE's Matrix Routing size ceiling varies by mode — Flexible, Region, or Profile — by synchronous versus asynchronous submission, and by entitlement. Quoting the largest number without the mode that produces it is misleading. See [Matrix Routing](/guides/matrix-routing). **Benchmark results we did not produce.** If we have not run the test on data we can describe, we will not report a number. **Competitor characterizations we cannot source.** Where a competitor's capability is unclear from public documentation, we say it is unclear. **And when a competitor ships a capability we said they lacked, we correct the page and say what changed.** Google Maps Platform added Large Vehicle Routing in August 2026. Several pages in this section asserted the absence of a commercial vehicle profile; those assertions were true when written and are not true now. They have been revised, with the revision noted rather than silently applied. ## Related Why architecture moves your bill by orders of magnitude and rate cards do not. Dual-running, shadow comparison, and rollback. Optimize before you migrate. You may not need to migrate. Once you have chosen a platform, choose the right primitive within it. ## Sources * [HERE Technologies documentation](https://www.here.com/docs) * [Placematic comparison hub](https://placematic.com/compare/) — commercial comparison and pricing overview *** Need to compare these platforms with your own request mix? Placematic can help you run a technical and cost evaluation using representative routes, addresses and production volumes. Placematic is an official HERE Technologies reseller and implementation partner. [Cost Reduction Audit](https://placematic.com/here-location-services/cost-reduction-audit/). # Autocomplete an Address Source: https://docs.placematic.com/examples/autocomplete-address Type-ahead address entry with HERE Geocoding & Search v7 — debouncing, request cancellation, and why /autocomplete is not /autosuggest. # Autocomplete an Address **Problem:** A user is typing an address. I need suggestions without billing per keystroke. **Undebounced, you bill once per character typed, per session, forever.** A twenty-character address is twenty transactions. This is the single largest unforced cost in consumer-facing location apps. ## Prerequisites * A HERE API key with Geocoding & Search entitlement * `export HERE_API_KEY="..."` **`/autocomplete` and `/autosuggest` are not synonyms.** | Endpoint | Host | Use for | | ------------------ | --------------------------------- | ---------------------------------------------------------- | | `/v1/autocomplete` | `autocomplete.search.hereapi.com` | **Structured address input.** Checkout, delivery, dispatch | | `/v1/autosuggest` | `autosuggest.search.hereapi.com` | **Broader discovery.** Places, categories, misspellings | Picking the wrong one produces a type-ahead that feels broken to users and correct to engineers. ## The code ```javascript Node.js / browser theme={null} const HOST = "https://autocomplete.search.hereapi.com/v1/autocomplete"; const API_KEY = process.env.HERE_API_KEY; // proxy this in the browser const DEBOUNCE_MS = 250; // 200–300ms. Not zero. const MIN_CHARS = 3; // "1" matches nothing useful let timer = null; let inFlight = null; /** * Debounced, cancellable address autocomplete. * * Two things prevent the bill: * 1. Debounce — one request per pause, not per keystroke * 2. AbortController — cancel the previous request when a newer one starts */ export function autocompleteAddress(query, { at, countryCode, limit = 5 }) { return new Promise((resolve, reject) => { clearTimeout(timer); if (query.length < MIN_CHARS) { resolve([]); return; } timer = setTimeout(async () => { // Cancel the previous in-flight request. The user has typed more; // its result is already stale. inFlight?.abort(); const ctrl = new AbortController(); inFlight = ctrl; const params = new URLSearchParams({ q: query, limit: String(limit), apiKey: API_KEY, }); // Bias toward the user's location. Improves relevance materially. if (at) params.set("at", at); // "lat,lng" if (countryCode) params.set("in", `countryCode:${countryCode}`); const timeout = setTimeout(() => ctrl.abort(), 5_000); try { const resp = await fetch(`${HOST}?${params}`, { signal: ctrl.signal }); if (resp.status === 403) throw new Error("Autocomplete not entitled"); if (!resp.ok) throw new Error(`HERE returned ${resp.status}`); const { items = [] } = await resp.json(); resolve(items.map((it) => ({ label: it.address.label, hereId: it.id, // ← store this. Resolve later with /lookup. resultType: it.resultType, highlights: it.highlights, // for bolding the matched substring }))); } catch (err) { if (err.name === "AbortError") return; // superseded. Not an error. reject(err); } finally { clearTimeout(timeout); } }, DEBOUNCE_MS); }); } ``` ```python Python theme={null} import os import requests HOST = "https://autocomplete.search.hereapi.com/v1/autocomplete" API_KEY = os.environ["HERE_API_KEY"] class EntitlementError(Exception): ... def autocomplete(query: str, at: str | None = None, country: str | None = None, limit: int = 5) -> list[dict]: """ Server-side autocomplete — e.g. behind your own debounced proxy. Debouncing belongs in the CLIENT. If you call this per keystroke from a backend, you have moved the problem, not solved it. """ if len(query) < 3: return [] params = {"q": query, "limit": limit, "apiKey": API_KEY} if at: params["at"] = at # "lat,lng" — biases relevance if country: params["in"] = f"countryCode:{country}" # ISO 3166-1 alpha-3 resp = requests.get(HOST, params=params, timeout=5) if resp.status_code == 403: raise EntitlementError("Autocomplete not entitled") resp.raise_for_status() return [ { "label": it["address"]["label"], "here_id": it["id"], "result_type": it["resultType"], } for it in resp.json().get("items", []) ] ``` ```bash cURL theme={null} # Address completion — checkout, delivery, dispatch curl -G 'https://autocomplete.search.hereapi.com/v1/autocomplete' \ --data-urlencode 'q=425 W Randolph' \ --data-urlencode 'at=41.8845,-87.6386' \ --data-urlencode 'in=countryCode:USA' \ --data-urlencode 'limit=5' \ --data-urlencode "apiKey=${HERE_API_KEY}" # Autosuggest — places, categories, misspellings. A DIFFERENT endpoint. curl -G 'https://autosuggest.search.hereapi.com/v1/autosuggest' \ --data-urlencode 'q=resta' \ --data-urlencode 'at=52.5200,13.4050' \ --data-urlencode 'limit=5' \ --data-urlencode "apiKey=${HERE_API_KEY}" ``` ## Response walkthrough ```json theme={null} { "items": [{ "title": "425 W Randolph St, Chicago, IL 60606-1506, United States", "id": "here:af:streetsection:hdZ6xBRUraY46IQZCqZidD:...", "resultType": "houseNumber", "address": { "label": "425 W Randolph St, Chicago, IL 60606-1506, United States" }, "highlights": { "title": [{ "start": 0, "end": 14 }], "address": { "label": [{ "start": 0, "end": 14 }] } } }] } ``` **`id`** — the HERE ID. Store it. When the user selects a suggestion, resolve it with `/v1/lookup` rather than re-geocoding the label string. **`highlights`** — character offsets of the matched substring. Bold them in the dropdown. **No `position`.** Autocomplete returns addresses, not coordinates. Resolve the selected item. The efficient flow: `/autocomplete` while typing (cheap, debounced) → user selects → `/lookup` by `id` (one call, exact) → cache the result permanently. Re-geocoding the label string with `/geocode` after the user has already handed you a HERE ID is a wasted call and a worse match. ## Common mistakes **No debounce.** One transaction per character. **No `AbortController`.** The user typed "425 W Ra" — the response for "425 W R" is already stale and you are still paying to receive it. **Firing below three characters.** `"1"` matches nothing useful and bills anyway. **Using `/autosuggest` for an address field.** It returns places and categories. Users get "Randolph Street Market" where they wanted an address. **Using `/autocomplete` for a search box.** No typo tolerance, no places. **Sending `q` and `qq` together.** HERE's own guidance: this produces inconsistent scoring. That applies to `/geocode`; keep autocomplete queries clean. **Not passing `at`.** Location bias improves relevance materially and costs nothing. **Re-geocoding the selected label** instead of `/lookup` by `id`. **Shipping the API key to the browser** without a domain restriction. Any key in frontend JavaScript is public. ## Production considerations **Debounce at 200–300ms.** Below 200ms you bill for typing pauses. Above 300ms it feels laggy. **Cancel superseded requests.** `AbortError` is a normal outcome, not a failure to log. **Proxy through your backend.** Keeps the key private, gives you a cache layer and per-tenant metering. Costs latency. Decide deliberately. **Restrict browser-exposed keys by domain.** If it has no restriction, it is now someone else's key and someone else's invoice. **Cache selections permanently**, keyed on the HERE ID. See [Caching Geocoding Results](/architecture/caching-geocoding-results). **Prevention beats correction.** An address entered from a validated suggestion does not need fixing later. This is the cheapest address-quality intervention available. See [Address Validation](/use-cases/address-validation). ## Related Seven endpoints, one correct choice per job. `/discover` and `/browse` — the endpoints autocomplete is not. Prevention at the source. Pattern 1: the keystroke is the wrong unit of work. ## HERE documentation * [Geocoding & Search v7 introduction](https://docs.here.com/geocoding-and-search/docs/introduction-to-here-geocoding-search-api-v7) * [Developer insights and best practices](https://www.here.com/learn/blog/here-geocoding-and-search-api-developer-insights-and-best-practices) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Batch Address Cleanup — One Million Records Source: https://docs.placematic.com/examples/batch-address-cleanup A complete pipeline: normalize, deduplicate, chunk, queue, submit, poll, handle partial failure, cache, export. And the step that removes 80% of the bill before any API call. # Batch Address Cleanup — One Million Records **Problem:** One million customer addresses. Coordinates, normalized form, confidence. Once. **Before any API call, run the preparation stage.** One million rows typically contains far fewer distinct addresses. Normalizing then deduplicating routinely reduces the billable set by a large fraction, and it costs one `SELECT DISTINCT`. Report *that* number before you report a timeline. It changes the conversation. ## The pipeline ```mermaid theme={null} flowchart TB subgraph Prep["Preparation — free, before any API call"] SRC[(source_addresses
1,000,000 rows)] NORM[Normalize] DEDUP[Deduplicate] FILT[WHERE geocoded_at IS NULL] COUNT[Report distinct count] end subgraph Queue["Job orchestration"] CHUNK[Deterministic chunks] Q[Queue — respects
concurrency limit] SUB[POST /v7/batch/jobs] STATE[(job_state
id persisted BEFORE poll)] end subgraph Poll["Poll with backoff"] ST[GET /jobs/id] TERM{success / failed /
cancelled?} end subgraph Collect["Retrieve"] ERR[GET /errors → 204 = ZERO] RES[GET /result → stream] MERGE[Merge into cache] EXC[Exception queue] DEL[DELETE job] end SRC --> NORM --> DEDUP --> FILT --> COUNT --> CHUNK --> Q --> SUB --> STATE STATE --> ST --> TERM TERM -->|no| ST TERM -->|yes| ERR --> RES RES --> MERGE ERR --> EXC MERGE --> DEL ``` ## Schema ```sql theme={null} -- The durable cache. This IS the system of record. CREATE TABLE geocode_cache ( normalized_address text PRIMARY KEY, lat double precision, lng double precision, access_lat double precision, -- road-network entry point access_lng double precision, label text, query_score real, -- 0..1 field_score jsonb, -- per-component confidence house_number_type text, -- 'PA' | 'interpolated' here_id text, map_release text, geocoded_at timestamptz NOT NULL DEFAULT now() ); -- Idempotency. Survives worker restarts. CREATE TABLE batch_jobs ( chunk_id int PRIMARY KEY, job_id text UNIQUE, -- persisted BEFORE the first poll status text NOT NULL, -- pending|submitted|running|succeeded|failed submitted_at timestamptz, row_ids bigint[] NOT NULL -- deterministic membership ); CREATE TABLE exception_queue ( raw_address text PRIMARY KEY, reason text, query_score real, queued_at timestamptz DEFAULT now() ); ``` ## Stage 1 — preparation ```sql theme={null} -- Normalize at WRITE time, then deduplicate. Order matters: -- deduplicating before normalization catches only exact string matches. WITH normalized AS ( SELECT id, trim(regexp_replace( lower( regexp_replace(raw_address, '\s+', ' ', 'g') ), '\m(st|ave|blvd|rd|apt)\M', CASE -- expand abbreviations WHEN true THEN 'street' END, 'g' )) AS norm FROM source_addresses ) INSERT INTO work_queue (normalized_address, source_ids) SELECT norm, array_agg(id) FROM normalized WHERE NOT EXISTS ( -- skip what we already have SELECT 1 FROM geocode_cache c WHERE c.normalized_address = normalized.norm ) GROUP BY norm; -- ← the deduplication -- Report this BEFORE promising a timeline. SELECT count(*) AS billable_addresses FROM work_queue; ``` Use `libpostal` for international normalization rather than a regex you will regret. It is self-hosted, removes normalization from your API bill entirely, and does not geocode. ## Stage 2 — chunk and queue ```python theme={null} import os, time, requests, psycopg2 BASE = "https://batch.search.hereapi.com/v7/batch/jobs" API_KEY = os.environ["HERE_API_KEY"] SERVICE_GEOCODE = "hrn:here:service::olp-here:search-geocode-7" CHUNK_SIZE = 50_000 MAX_CONCURRENT_JOBS = 3 # read HERE's limits page. It sets your parallelism. class EntitlementError(Exception): ... def make_chunks(conn): """ DETERMINISTIC. Stable ordering + fixed size means a restarted process reconstructs the same chunks. A random shuffle does not. """ with conn.cursor() as cur: cur.execute(""" INSERT INTO batch_jobs (chunk_id, status, row_ids) SELECT (row_number() OVER (ORDER BY normalized_address) - 1) / %s AS chunk_id, 'pending', array_agg(id ORDER BY id) FROM work_queue GROUP BY chunk_id ON CONFLICT (chunk_id) DO NOTHING """, (CHUNK_SIZE,)) conn.commit() def submit_next(conn) -> str | None: """ Respects the concurrency limit. 429 on start means QUEUE — not retry. Firing retries in a tight loop achieves nothing except log volume. """ with conn.cursor() as cur: cur.execute("SELECT count(*) FROM batch_jobs WHERE status IN ('submitted','running')") if cur.fetchone()[0] >= MAX_CONCURRENT_JOBS: return None cur.execute("SELECT chunk_id, row_ids FROM batch_jobs " "WHERE status='pending' ORDER BY chunk_id LIMIT 1 " "FOR UPDATE SKIP LOCKED") row = cur.fetchone() if not row: return None chunk_id, row_ids = row payload = build_payload(conn, row_ids) r = requests.post(BASE, params={ "serviceHrn": SERVICE_GEOCODE, "inputDelimiter": "|", "outputDelimiter": "|", "outputColumns": "|".join([ "position", "title", "id", "addressLabel", "addressPostalCode", "addressCountryCode", ]), "outputType": "csv", "startJob": "true", "apiKey": API_KEY, }, data=payload.encode("utf-8"), headers={"Content-Type": "text/plain", "Accept": "application/json"}, timeout=180) if r.status_code == 403: raise EntitlementError("Batch not entitled") if r.status_code == 429: return None # concurrency limit. Queue. Do not hammer. r.raise_for_status() job_id = r.json()["id"] # ── PERSIST BEFORE POLLING. ──────────────────────────────────────────── # If the worker dies here, the job is RUNNING on HERE's side regardless. # Resubmitting a 50k-record chunk bills 50k records again. with conn.cursor() as cur: cur.execute("UPDATE batch_jobs SET job_id=%s, status='submitted', " "submitted_at=now() WHERE chunk_id=%s", (job_id, chunk_id)) conn.commit() return job_id def build_payload(conn, row_ids) -> str: with conn.cursor() as cur: cur.execute("SELECT id, normalized_address, country FROM work_queue " "WHERE id = ANY(%s) ORDER BY id", (row_ids,)) lines = ["recId|q|country"] lines += [f"{r[0]}|{r[1]}|{r[2]}" for r in cur.fetchall()] return "\n".join(lines) ``` ## Stage 3 — poll, and the three codes that are not errors ```python theme={null} def poll_all(conn): """Exponential backoff. A 40-minute job does not need checking every 2s.""" with conn.cursor() as cur: cur.execute("SELECT chunk_id, job_id FROM batch_jobs " "WHERE status IN ('submitted','running')") active = cur.fetchall() for chunk_id, job_id in active: r = requests.get(f"{BASE}/{job_id}", params={"apiKey": API_KEY}, timeout=30) r.raise_for_status() status = r.json()["status"] if status == "success": _collect(conn, chunk_id, job_id) elif status in ("failed", "cancelled"): _mark(conn, chunk_id, status) else: _mark(conn, chunk_id, "running") def _collect(conn, chunk_id: int, job_id: str): # ── /errors: HTTP 204 means ZERO ERRORS. Not a failure. ─────────────── e = requests.get(f"{BASE}/{job_id}/errors", params={"apiKey": API_KEY}, timeout=60) if e.status_code == 204: errors = None # celebrate else: e.raise_for_status() errors = e.text _queue_exceptions(conn, errors) # partial success. Do NOT discard the rest. # ── /result: HTTP 404 means NOT READY. Poll status first. ───────────── path = f"/tmp/{job_id}.csv" res = requests.get(f"{BASE}/{job_id}/result", params={"apiKey": API_KEY}, stream=True, timeout=600) if res.status_code == 404: raise RuntimeError("Results not ready — status was success?") res.raise_for_status() # Stream. One million records do not fit in memory. with open(path, "wb") as f: for chunk in res.iter_content(chunk_size=1 << 20): f.write(chunk) _merge_into_cache(conn, path) requests.delete(f"{BASE}/{job_id}", params={"apiKey": API_KEY}, timeout=30) _mark(conn, chunk_id, "succeeded") ``` | Response | Meaning | Action | | ------------------ | --------------------- | ----------------------- | | `429` on start | Concurrency limit | **Queue.** Not a fault | | `204` on `/errors` | **Zero errors** | Celebrate. Do not retry | | `404` on `/result` | Job not succeeded yet | Poll status first | Code that treats any non-`200` as failure will log a perfectly successful job as broken, every night, until someone reads the spec. ## Stage 4 — partial success and the confidence threshold ```python theme={null} MIN_QUERY_SCORE = 0.90 # calibrate against YOUR ground truth. Do not port. def _merge_into_cache(conn, csv_path: str): """ Successes merge. Sub-threshold results go to a human. A pipeline that treats geocoding as binary — succeeded or failed — will persist low-confidence fallbacks as truth. That corruption is silent, permanent, and propagates into every routing decision and every analytical aggregate downstream. """ good, review = [], [] for row in stream_csv(csv_path): if row["query_score"] >= MIN_QUERY_SCORE: good.append(row) else: review.append(row) with conn.cursor() as cur: cur.executemany(""" INSERT INTO geocode_cache (normalized_address, lat, lng, access_lat, access_lng, label, query_score, field_score, house_number_type, here_id, geocoded_at) VALUES (%(norm)s,%(lat)s,%(lng)s,%(alat)s,%(alng)s,%(label)s, %(query_score)s,%(field_score)s,%(hn_type)s,%(here_id)s,now()) ON CONFLICT (normalized_address) DO UPDATE SET lat=EXCLUDED.lat, lng=EXCLUDED.lng, query_score=EXCLUDED.query_score, geocoded_at=now() """, good) cur.executemany(""" INSERT INTO exception_queue (raw_address, reason, query_score) VALUES (%(norm)s, 'below_confidence_threshold', %(query_score)s) ON CONFLICT (raw_address) DO NOTHING """, review) conn.commit() ``` **Storing a coordinate without its confidence converts a probabilistic estimate into a fact.** Every downstream consumer treats it as truth. `query_score: 0.95` with `field_score.houseNumber: 0.4` matched the street confidently and the house number badly. That is a failed delivery. Persist `field_score`. ## Stage 5 — monitoring Nobody is watching a nightly job. Instrument it as if it will fail at 3am, because it will. | Metric | Alert when | | ------------------------------ | -------------------------------------------- | | Jobs by state, time in state | Job running longer than expected | | `429` rate on submission | Sustained → concurrency budget misconfigured | | Records submitted vs returned | Divergence | | **`query_score` distribution** | **Shifts** — before it shows as failures | | Deduplication ratio | Sudden drop → source changed | | Jobs not deleted | Accumulation | | Cost per **distinct** address | Not per row | **Alert on the confidence distribution shifting, not just on errors.** A map release, a source-system change, or a normalization regression shows up as a distribution shift long before it shows up as a failure. **Webhooks exist in beta**, with placeholders like `${JOB_ID}` and `${JOB_STATUS}`. If your pipeline's correctness depends on a webhook arriving, build the polling fallback anyway. ## Stage 6 — export, and the steady state ```sql theme={null} -- Backfill the source table from the cache. UPDATE source_addresses s SET lat = c.lat, lng = c.lng, access_lat = c.access_lat, access_lng = c.access_lng, geocoded_at = c.geocoded_at FROM geocode_cache c, work_queue w WHERE w.normalized_address = c.normalized_address AND s.id = ANY(w.source_ids); ``` **The end state:** every address has coordinates, a normalized form, a confidence score, and a timestamp. New addresses arrive at a trickle and are geocoded in real time. **The batch job runs once, at migration, and then rarely.** On the second run, `WHERE geocoded_at IS NULL` returns almost nothing. Architect the pipeline for the backfill. It will spend its life doing very little. **Do not use a TTL.** A thirty-day expiry invalidates a stable rooftop match for a building that has stood since 1904, and does nothing about the subdivision that opened yesterday. Invalidate on map release, low confidence, explicit correction, or a failed delivery. **A permanent geocode cache is a permanent record of where people live.** Coordinates that identify a residence are personal data. Right-to-erasure applies to the cache; it is not exempt because it is "just a cache." Consider separating the *address → coordinate* mapping (a fact about the world) from the *person → address* mapping (personal data). Delete the second; keep the first. In a **multi-tenant** system, a shared geocode cache is a data leak — hit patterns are a side channel. Key by tenant. ## Common mistakes **Not deduplicating.** Paying for repetition. **Not normalizing before deduplicating.** Catches only exact matches. **Treating Batch API as a bulk endpoint.** It is a job lifecycle. **Losing the job ID on restart.** Full rebill. **Retrying `204`.** Zero errors. **Aggressively retrying `404`.** The job has not finished. **Hammering after `429`.** **Assuming all-or-nothing.** Read `/errors`. Handle partial success. **Never deleting jobs.** They accumulate. **Loading a million results into memory.** Stream. **Persisting low-confidence matches as truth.** **A time-based TTL.** **Not writing results into a durable cache.** You will do this again next quarter, and pay again. **Onboarding jobs starving nightly enrichment.** Concurrency is per-contract. Prioritize. ## Related Queues, idempotency, and what to monitor. Normalization, invalidation, and the privacy question. The single-job lifecycle in isolation. Why geocoding and deliverability are different products. ## HERE documentation * [Use geocoding with HERE Batch API v7](https://docs.here.com/geocoding-and-search/docs/geocoding) * [Batch API v7 quick start](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/batch-api-quick-start.html) * [Limits and performance](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/limits-and-performance.html) * [Job lifecycle](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/job-lifecycle.html) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Batch Geocoding Source: https://docs.placematic.com/examples/batch-geocoding Four million addresses through HERE Batch API v7 — the job lifecycle, serviceHrn, idempotency across worker restarts, and the status codes that are not errors. # Batch Geocoding **Problem:** I have four million addresses. Nothing is waiting for the result. **Deduplicate first.** Four million rows containing nine hundred thousand distinct addresses bills for four million. This is one `SELECT DISTINCT` on a normalized column, it is free, and it is skipped constantly. ## Prerequisites * A HERE API key with Batch entitlement * `export HERE_API_KEY="..."` **Batch API v7 is at `batch.search.hereapi.com/v7/batch/jobs`.** It is driven by a `serviceHrn` parameter that selects which service processes the job. If you are reading a tutorial using `batch.geocoder.ls.hereapi.com/6.2/jobs` with `action=run` and `outcols=`, that is the **legacy Batch Geocoder API 6.2**. Different host, different parameters, different response format (XML). Prefer v7 for new work. ## The code ```python Python theme={null} import os import time import requests BASE = "https://batch.search.hereapi.com/v7/batch/jobs" API_KEY = os.environ["HERE_API_KEY"] # Selects the service that processes the job. SERVICE_GEOCODE = "hrn:here:service::olp-here:search-geocode-7" class EntitlementError(Exception): ... def build_input(records: list[tuple[str, str, str]]) -> str: """ Pipe-delimited, with a header row. records: [(recId, query, countryCode), ...] Deduplicate and normalize BEFORE you get here. """ lines = ["recId|q|country"] lines += [f"{rid}|{q}|{c}" for rid, q, c in records] return "\n".join(lines) def submit_job(payload: str, start_immediately: bool = True) -> dict: """ POST the input as the request body. Returns {id, status, href}. PERSIST THE JOB ID BEFORE POLLING. If your worker dies between here and the first poll, the job is running on HERE's side regardless. Resubmitting a 4M-record job bills 4M records again. """ params = { "serviceHrn": SERVICE_GEOCODE, "inputDelimiter": "|", "outputDelimiter": "|", "outputColumns": "|".join([ "position", "title", "id", "addressLabel", "addressHouseNumber", "addressStreet", "addressCity", "addressPostalCode", "addressCountryCode", ]), "outputType": "csv", "startJob": str(start_immediately).lower(), "apiKey": API_KEY, } resp = requests.post( BASE, params=params, data=payload.encode("utf-8"), headers={"Content-Type": "text/plain", "Accept": "application/json"}, timeout=120, ) if resp.status_code == 403: raise EntitlementError("Batch not entitled") if resp.status_code == 429: # Concurrent job limit. QUEUE. Do not hammer. raise RuntimeError("Concurrency limit — queue this job") resp.raise_for_status() return resp.json() # {"id": ..., "status": "submitted"|"queued", "href": ...} def poll_status(job_id: str, max_wait_s: int = 7200) -> str: """Poll with backoff. Returns terminal status.""" backoff, waited = 5.0, 0.0 while waited < max_wait_s: resp = requests.get(f"{BASE}/{job_id}", params={"apiKey": API_KEY}, timeout=30) resp.raise_for_status() status = resp.json()["status"] if status in ("success", "failed", "cancelled"): return status time.sleep(backoff) waited += backoff backoff = min(backoff * 1.4, 60.0) raise TimeoutError(f"Job {job_id} not terminal after {max_wait_s}s") def fetch_errors(job_id: str) -> str | None: """ 204 means ZERO ERRORS. It is not a failure. Code that treats any non-200 as failure will log a perfectly successful job as broken, every night, until someone reads the spec. """ resp = requests.get(f"{BASE}/{job_id}/errors", params={"apiKey": API_KEY}, timeout=60) if resp.status_code == 204: return None # Celebrate. resp.raise_for_status() return resp.text def fetch_results(job_id: str, out_path: str) -> None: """ 404 means the job has not succeeded yet. Poll status first. Stream — 4M records do not fit in memory. """ resp = requests.get(f"{BASE}/{job_id}/result", params={"apiKey": API_KEY}, stream=True, timeout=300) if resp.status_code == 404: raise RuntimeError("Results not ready — job has not succeeded") resp.raise_for_status() with open(out_path, "wb") as f: for chunk in resp.iter_content(chunk_size=1 << 20): f.write(chunk) def delete_job(job_id: str) -> None: """Step six. Jobs accumulate.""" requests.delete(f"{BASE}/{job_id}", params={"apiKey": API_KEY}, timeout=30) if __name__ == "__main__": records = [ ("0001", "Invalidenstraße 116 10115 Berlin", "DEU"), ("0002", "425 W Randolph St Chicago IL 60606", "USA"), ("0003", "One Main Street Cambridge MA 02142", "USA"), ] job = submit_job(build_input(records)) job_id = job["id"] # ---- PERSIST job_id HERE, before the next line. ---- print(f"Job {job_id} — persist this") status = poll_status(job_id) if status != "success": raise SystemExit(f"Job ended: {status}") errors = fetch_errors(job_id) if errors: print("Partial failure — route these to the exception queue:") print(errors) # Do NOT discard 2.9M good results over 100k bad ones fetch_results(job_id, "geocoded.csv") delete_job(job_id) ``` ```bash cURL theme={null} # 1. Create + start in one call (startJob=true) resp=$(curl -sX POST \ "https://batch.search.hereapi.com/v7/batch/jobs?\ serviceHrn=hrn:here:service::olp-here:search-geocode-7&\ inputDelimiter=|&outputDelimiter=|&\ outputColumns=position|title|id&\ outputType=csv&startJob=true&apiKey=${HERE_API_KEY}" \ -H 'Accept: application/json' \ -H 'Content-Type: text/plain' \ --data 'recId|q|country 0001|Invalidenstraße 116 10115 Berlin|DEU 0002|425 W Randolph St Chicago IL 60606|USA') jobId=$(echo "$resp" | jq -r '.id') echo "PERSIST THIS: $jobId" # 2. Poll status until "success" curl -s "https://batch.search.hereapi.com/v7/batch/jobs/${jobId}?apiKey=${HERE_API_KEY}" | jq # 3. Errors. HTTP 204 = zero errors. Not a failure. curl -sw '%{http_code}' \ "https://batch.search.hereapi.com/v7/batch/jobs/${jobId}/errors?apiKey=${HERE_API_KEY}" # 4. Results. 404 until the job succeeds. curl -s -o geocoded.csv \ "https://batch.search.hereapi.com/v7/batch/jobs/${jobId}/result?apiKey=${HERE_API_KEY}" # 5. Clean up curl -sX DELETE \ "https://batch.search.hereapi.com/v7/batch/jobs/${jobId}?apiKey=${HERE_API_KEY}" ``` ## Response walkthrough Submission: ```json theme={null} { "id": "", "serviceHrn": "hrn:here:service::olp-here:search-geocode-7", "billingTags": [], "status": "submitted", "href": "https://batch.search.hereapi.com/v7/batch/jobs/" } ``` **`status`** progresses `submitted` → `queued` → `running` → `success` | `failed` | `cancelled`. **`billingTags`** let you attribute cost per tenant or per customer. Set them if you run a multi-tenant platform. You cannot price a feature whose cost you cannot attribute. ## The three status codes that are not errors | Response | Meaning | Correct action | | ------------------ | ------------------------- | ----------------------- | | `429` on job start | Concurrency limit reached | **Queue.** Not a fault | | `204` on `/errors` | **Zero errors** | Celebrate. Do not retry | | `404` on `/result` | Job has not succeeded yet | Poll status first | Code that treats any non-`200` as failure will log a perfectly successful job as broken, every night, until someone reads the specification. ## Idempotency The pipeline must survive a worker restart without resubmitting. chunk created → Pending POST /jobs → Submitted ← persist job\_id HERE, before polling │ ├─ crash → resume: job\_id exists, DO NOT resubmit ▼ GET /jobs/ → Running (poll with backoff) ▼ Succeeded → fetch errors → fetch results → DELETE **Persist the chunk-to-job mapping.** On resume, retrieve results for jobs already submitted; submit only chunks with no job. **Make chunking deterministic.** Stable ordering plus fixed size means a restarted process reconstructs the same chunks. A random shuffle does not. ## Common mistakes **Not deduplicating.** Paying for repetition. **Not normalizing before deduplicating.** Catches only exact string matches. **Treating this as a bulk endpoint.** It is a job lifecycle. **Losing the job ID on restart.** Full rebill. **Retrying `204`.** Zero errors. **Aggressively retrying `404`.** The job has not finished. **Hammering after `429`.** Concurrency limit. Queue. **Assuming all-or-nothing.** Read `/errors`. Handle partial success. **Never deleting jobs.** They accumulate. **Loading four million results into memory.** Stream. **Not writing results into a durable cache.** You will do this again next quarter, and pay again. **Onboarding jobs starving nightly enrichment.** Batch concurrency is per-contract. ## Production considerations **Order of operations, before any API call:** 1. Normalize (trim, case-fold, expand abbreviations) 2. Deduplicate 3. `WHERE geocoded_at IS NULL` 4. Count what remains — and report *that* number before you report a timeline The remaining count is usually a fifth of what you were handed. It changes the conversation with whoever asked. **Persist the job ID before the first poll.** **Poll with exponential backoff.** A forty-minute job does not need checking every two seconds. **Queue against the concurrency limit.** Read [Batch API limits and performance](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/limits-and-performance.html) before designing the pipeline; the limits determine your parallelism, not the reverse. **Use gzip on large inputs.** Supported, and it materially reduces upload time. **Handle partial success.** Successes merge into the cache; failures go to an exception queue with an owner. **Persist the confidence score.** A record that "succeeded" with a city-centroid fallback is a different outcome from a rooftop match. **Webhooks exist in beta.** If your pipeline's correctness depends on a webhook arriving, build the polling fallback anyway. **Write results into a durable cache.** The point of batch geocoding is that you never geocode that address again. ## Related The lifecycle, the limits, and the status codes in detail. Queues, retries, monitoring, and what to alert on. Where the output of this pipeline must land. The real-time path, for the trickle of new addresses. ## HERE documentation * [Use geocoding with HERE Batch API v7](https://docs.here.com/geocoding-and-search/docs/geocoding) * [Batch API v7 quick start](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/batch-api-quick-start.html) * [Job lifecycle](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/job-lifecycle.html) * [Limits and performance](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/limits-and-performance.html) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Calculate an Isochrone Source: https://docs.placematic.com/examples/calculate-isochrone Drive-time and consumption isolines with HERE Isoline Routing v8 — flexible polyline decoding, multiple range bands in one call, and materializing the result. # Calculate an Isochrone **Problem:** I need the polygon of everywhere reachable from a point within 20 minutes. Compute this **once, offline**, and store the polygon. If your system calls this API during a checkout serviceability check, you have built a spatial database with an API bill attached. The whole point of an isoline is that it is a materialized artifact. See [Delivery Zones](/use-cases/delivery-zones). ## Prerequisites * A HERE API key with Isoline Routing entitlement * `export HERE_API_KEY="..."` * A Flexible Polyline decoder ([HERE's reference implementations](https://github.com/heremaps/flexible-polyline)) **Isoline Routing is its own API**, at `isoline.router.hereapi.com`, not an endpoint on the Routing API. This changed in v8. If you are reading a tutorial using `isoline.route.ls.hereapi.com/routing/7.2/calculateisoline.json`, you are reading v7. Migration notes: `start` → `origin`; `range` + `rangeType` → `range[type]` + `range[values]`; `resolution` → `shape[maxResolution]` (and it is now 3× as precise); XML is gone; **truck parameters are deprecated in favour of `vehicle`**. ## The code ```python Python theme={null} import os import time import requests from flexpolyline import decode # pip install flexpolyline HOST = "https://isoline.router.hereapi.com/v8/isolines" API_KEY = os.environ["HERE_API_KEY"] class EntitlementError(Exception): ... class NoIsolineError(Exception): """isolineCalculationFailed — e.g. origin in the ocean.""" def isolines( origin: str, # "lat,lng" range_values: list[int], # seconds (time), metres (distance), Wh (consumption) range_type: str = "time", # "time" | "distance" | "consumption" transport_mode: str = "car", departure_time: str | None = None, max_points: int = 200, # >=30 enforced; >100 recommended for quality ) -> list[dict]: """ Compute one or more isoline bands from a single origin. Multiple range values in ONE call. Do not loop this endpoint to get 10/20/30-minute bands — request all three at once. """ params = { "transportMode": transport_mode, "origin": origin, "range[type]": range_type, # Comma-separated. One request, three polygons. "range[values]": ",".join(str(v) for v in range_values), "shape[maxPoints]": max_points, "apiKey": API_KEY, } if departure_time: # A 3am polygon applied to 5pm operations is a fiction. params["departureTime"] = departure_time backoff = 1.0 for _ in range(3): resp = requests.get(HOST, params=params, timeout=30) if resp.status_code == 403: raise EntitlementError("Isoline Routing not entitled") if resp.status_code == 429 or resp.status_code >= 500: time.sleep(backoff); backoff *= 2; continue resp.raise_for_status() break else: raise RuntimeError("Isoline failed after retries") body = resp.json() # Failure is signalled in `notices`, not in the HTTP status. if not body.get("isolines"): raise NoIsolineError(str(body.get("notices", []))) out = [] for iso in body["isolines"]: # Each isoline has 1..n polygons. Multiple polygons occur when the # reachable area is disconnected — islands, ferries, one-way systems. rings = [] for poly in iso["polygons"]: # Flexible Polyline, NOT Google's encoded polyline algorithm. rings.append(decode(poly["outer"])) out.append({ "range_type": iso["range"]["type"], "range_value": iso["range"]["value"], "rings": rings, # list of [(lat, lng), ...] }) return out if __name__ == "__main__": # Three delivery bands, one request, computed at peak departure. bands = isolines( origin="41.8845,-87.6386", range_values=[600, 1200, 1800], # 10, 20, 30 minutes range_type="time", departure_time="2026-07-15T17:30:00", ) for b in bands: n = sum(len(r) for r in b["rings"]) print(f"{b['range_value']}s band: {len(b['rings'])} polygon(s), {n} vertices") # Next: simplify, then INSERT into PostGIS. Never call this at request time. ``` ```javascript Node.js theme={null} import { decode } from "@here/flexpolyline"; const HOST = "https://isoline.router.hereapi.com/v8/isolines"; const API_KEY = process.env.HERE_API_KEY; class EntitlementError extends Error {} class NoIsolineError extends Error {} const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); export async function isolines(origin, rangeValues, { rangeType = "time", transportMode = "car", departureTime, maxPoints = 200, } = {}) { const params = new URLSearchParams({ transportMode, origin, "range[type]": rangeType, "range[values]": rangeValues.join(","), // one call, many bands "shape[maxPoints]": String(maxPoints), apiKey: API_KEY, }); if (departureTime) params.set("departureTime", departureTime); let backoff = 1000, resp; for (let i = 0; i < 3; i++) { const ctrl = new AbortController(); const t = setTimeout(() => ctrl.abort(), 30_000); try { resp = await fetch(`${HOST}?${params}`, { signal: ctrl.signal }); } finally { clearTimeout(t); } if (resp.status === 403) throw new EntitlementError("Isoline not entitled"); if (resp.status === 429 || resp.status >= 500) { await sleep(backoff + Math.random() * 250); backoff *= 2; continue; } if (!resp.ok) throw new Error(`HERE returned ${resp.status}`); break; } const body = await resp.json(); if (!body.isolines?.length) throw new NoIsolineError(JSON.stringify(body.notices ?? [])); return body.isolines.map((iso) => ({ rangeType: iso.range.type, rangeValue: iso.range.value, rings: iso.polygons.map((p) => decode(p.outer).polyline), })); } ``` ```bash cURL theme={null} # Three time bands in one request curl -gX GET 'https://isoline.router.hereapi.com/v8/isolines' \ --data-urlencode 'transportMode=car' \ --data-urlencode 'origin=41.8845,-87.6386' \ --data-urlencode 'range[type]=time' \ --data-urlencode 'range[values]=600,1200,1800' \ --data-urlencode 'shape[maxPoints]=200' \ --data-urlencode "apiKey=${HERE_API_KEY}" \ -G # Truck service area — smaller and differently shaped than a car isoline. # NOTE: vehicle[...] supersedes the deprecated truck[...] parameters. curl -gX GET 'https://isoline.router.hereapi.com/v8/isolines' \ --data-urlencode 'transportMode=truck' \ --data-urlencode 'origin=41.8845,-87.6386' \ --data-urlencode 'range[type]=time' \ --data-urlencode 'range[values]=1800' \ --data-urlencode 'vehicle[height]=410' \ --data-urlencode 'vehicle[grossWeight]=35000' \ --data-urlencode "apiKey=${HERE_API_KEY}" \ -G # EV consumption isoline — "everywhere reachable on this charge", in Wh curl -gX GET 'https://isoline.router.hereapi.com/v8/isolines' \ --data-urlencode 'transportMode=car' \ --data-urlencode 'origin=41.8845,-87.6386' \ --data-urlencode 'range[type]=consumption' \ --data-urlencode 'range[values]=20000' \ --data-urlencode "apiKey=${HERE_API_KEY}" \ -G ``` ## Response walkthrough ```json theme={null} { "departure": { "place": { "type": "place", "location": {...} } }, "isolines": [{ "range": { "type": "time", "value": 600 }, "polygons": [{ "outer": "BG..." }] }] } ``` **`range[values]` units differ by `range[type]`:** | `range[type]` | Unit | | ------------- | ------- | | `time` | seconds | | `distance` | metres | | `consumption` | Wh | **`polygons` is an array.** More than one means the reachable area is **disconnected** — separated by water, a ferry, or a one-way system. A `connections` list identifies which polygons connect and how. Do not assume one ring. **Geometry is Flexible Polyline.** Not Google's encoded polyline. Use [HERE's decoders](https://github.com/heremaps/flexible-polyline). **Failure returns `notices`:** ```json theme={null} { "notices": [{ "title": "Isolines could not be calculated.", "code": "isolineCalculationFailed" }] } ``` ## Common mistakes **Calling this at request time.** Materialize it. **Looping the endpoint for each band.** `range[values]=600,1200,1800` returns three polygons in one call. **Assuming one polygon per isoline.** Disconnected components exist. **Decoding with a Google polyline library.** Different algorithm. **Requesting `distance` when you meant `time`.** Both valid. One answers your business question. **Ignoring `departureTime`.** A 3am polygon is not your delivery zone at 6pm. **Using car mode for a truck service area.** Trucks cannot use every road in the polygon. **`shape[maxPoints]` below 100.** HERE enforces a minimum of 30 and recommends above 100. Quality degrades below that. **Assigning customers to stores by polygon membership.** Two stores' 20-minute polygons overlap. Assign by travel time from a matrix. ## Production considerations **Compute once, store in PostGIS, query with `ST_Contains`.** A 400-store network with three bands is 1,200 calls *per quarter*. The cost is bounded by store count — a number you already know. **Simplify before storage.** The polygon was an approximation with a resolution parameter to begin with. Smaller geometry, faster containment queries, no accuracy you were entitled to. **Store multiple bands as separate rows.** Delivery pricing by band becomes a lookup. **Recompute on events, not on a timer.** New location, closed location, changed service promise, significant map release. **Version them.** When a customer disputes "you delivered here last month," you need to know which polygon was live. **From-origin ≠ to-destination.** "Which customers can we reach in 30 minutes" and "which customers can reach us" are different polygons where one-way streets exist. Ask the question you mean. ## Related Range types, resolution, and why the polygon is an approximation. The materialization pattern this entire example exists to enable. Trade areas, cannibalization, and the overlap join. Pattern 3: precompute and materialize. ## HERE documentation * [Isoline Routing v8 get started](https://docs.here.com/routing/docs/isoline-v8-get-started) * [Isoline response structure](https://docs.here.com/routing/docs/isoline-v8-distance-isoline) * [Isoline v7 → v8 migration guide](https://docs.here.com/routing/docs/isoline-v8-intro) * [Flexible Polyline decoders](https://github.com/heremaps/flexible-polyline) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Calculate a Car Route Source: https://docs.placematic.com/examples/car-route Passenger vehicle routing with HERE Routing v8 — departure time, explicit return fields, and the 200 response that means no route was found. # Calculate a Car Route **Problem:** I need a driving route between two points, with a duration I can show a user. ## Prerequisites * A HERE API key with Routing entitlement * `export HERE_API_KEY="..."` ## The code ```python Python theme={null} import os import time import requests HOST = "https://router.hereapi.com/v8/routes" API_KEY = os.environ["HERE_API_KEY"] class EntitlementError(Exception): """403 — valid key, missing entitlement. Never retry.""" class NoRouteError(Exception): """HTTP 200, empty routes. A legitimate outcome.""" def car_route( origin: str, # "lat,lng" destination: str, # "lat,lng" departure_time: str | None = None, # ISO 8601, or None for now retries: int = 3, ) -> dict: """ Calculate a passenger-car route. `return` is set explicitly. Requesting `polyline,actions,instructions` when you consume only a duration inflates the payload by orders of magnitude. """ params = { "transportMode": "car", "origin": origin, # lat,lng — NOT lng,lat "destination": destination, "return": "summary,polyline", # Explicit. Always. "apiKey": API_KEY, } if departure_time: # Traffic is evaluated at this time, not at request time. # A route computed at 2pm for a 6pm departure inherits 2pm traffic # unless you say otherwise. params["departureTime"] = departure_time backoff = 1.0 for _ in range(retries): resp = requests.get(HOST, params=params, timeout=15) if resp.status_code == 403: raise EntitlementError("Routing not entitled for this key") if resp.status_code in (429,) or resp.status_code >= 500: time.sleep(backoff) backoff *= 2 continue resp.raise_for_status() break else: raise RuntimeError("Routing failed after retries") body = resp.json() # CRITICAL: HTTP 200 does not mean a route was found. routes = body.get("routes", []) if not routes: notices = body.get("notice", []) raise NoRouteError(f"No route: {notices}") section = routes[0]["sections"][0] return { "duration_s": section["summary"]["duration"], "length_m": section["summary"]["length"], "polyline": section.get("polyline"), } if __name__ == "__main__": try: r = car_route("41.8845,-87.6386", "41.9028,-87.6317") print(f"{r['duration_s']}s over {r['length_m']}m") except NoRouteError as e: print(f"Unroutable: {e}") # Do NOT retry except EntitlementError as e: print(f"Licensing: {e}") # Do NOT retry ``` ```javascript Node.js theme={null} const HOST = "https://router.hereapi.com/v8/routes"; const API_KEY = process.env.HERE_API_KEY; class EntitlementError extends Error {} class NoRouteError extends Error {} const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); export async function carRoute(origin, destination, { departureTime, retries = 3 } = {}) { const params = new URLSearchParams({ transportMode: "car", origin, // "lat,lng" destination, return: "summary,polyline", // Explicit apiKey: API_KEY, }); if (departureTime) params.set("departureTime", departureTime); let backoff = 1000; let resp; for (let i = 0; i < retries; i++) { const ctrl = new AbortController(); const timer = setTimeout(() => ctrl.abort(), 15_000); try { resp = await fetch(`${HOST}?${params}`, { signal: ctrl.signal }); } finally { clearTimeout(timer); } if (resp.status === 403) throw new EntitlementError("Routing not entitled"); if (resp.status === 429 || resp.status >= 500) { await sleep(backoff + Math.random() * 250); backoff *= 2; continue; } if (!resp.ok) throw new Error(`HERE returned ${resp.status}`); break; } const body = await resp.json(); // HTTP 200 with an empty routes array is a valid response. if (!body.routes?.length) { throw new NoRouteError(JSON.stringify(body.notice ?? [])); } const { summary, polyline } = body.routes[0].sections[0]; return { durationS: summary.duration, lengthM: summary.length, polyline }; } ``` ```bash cURL theme={null} curl -gX GET 'https://router.hereapi.com/v8/routes' \ --data-urlencode 'transportMode=car' \ --data-urlencode 'origin=41.8845,-87.6386' \ --data-urlencode 'destination=41.9028,-87.6317' \ --data-urlencode 'return=summary,polyline' \ --data-urlencode "apiKey=${HERE_API_KEY}" \ -G ``` ## Response walkthrough ```json theme={null} { "routes": [{ "id": "e9942278-...", "sections": [{ "id": "faeea7a2-...", "type": "vehicle", "summary": { "duration": 412, "length": 3820 }, "polyline": "BG..." }] }] } ``` `duration` is seconds. `length` is metres. `polyline` is HERE's flexible polyline encoding — decode it with HERE's published algorithm, not Google's. **When no route exists:** ```json theme={null} { "notice": [{ "title": "Route calculation failed: Couldn't find a route.", "code": "routeCalculationFailed" }], "routes": [] } ``` **HTTP status is `200`.** Checking `resp.ok` swallows this entirely. Always check `routes.length` and read `notice`. The classic trigger is an ocean between origin and destination. In truck routing, it means no *legal* path exists — which is correct, and must not be retried with relaxed constraints. ## Common mistakes **Reversing coordinate order.** HERE takes `lat,lng`. GeoJSON is `lng,lat`. This bites once, and it is always at 2am. **Treating `200` as success.** **Omitting `return`.** You get a default shape, which may change. Be explicit. **Requesting `instructions` for a backend ETA.** Nothing consumes them. **Letting `departureTime` default when the departure is in the future.** A route computed at 2pm for a 6pm departure inherits 2pm traffic. **Unencoded waypoint attributes.** Values like `nameHint` may contain `&` and `!`, which collide with URL control characters. **Retrying `403`.** Permanently futile. **Prototyping on `car` and shipping `truck`.** Different engine, different results. See [Truck Route](/examples/truck-route). ## Production considerations **Cache geometry and ETAs separately.** The path between two fixed points is stable for hours. The duration is not. One TTL for both serves stale arrival times to drivers. **Do not re-route on every GPS ping to refresh an ETA.** Recompute the remaining duration from the polyline and current position — arithmetic on data you already hold. Call routing when the driver deviates. See [ETA Calculation](/use-cases/eta-calculation). **If you are calling this in a loop**, stop. You are building a cost table and want [Distance Matrix](/examples/distance-matrix). A 20×500 problem is 10,000 routing calls or one matrix call. **Set a timeout.** Routes with many waypoints and traffic enabled are not instantaneous. **Some transport modes are beta.** `bicycle`, `bus`, and `privateBus` carry beta status with limited functionality. Verify before shipping. ## Related Transport modes, `return` fields, and error semantics. The loop that costs orders of magnitude. Same endpoint. Entirely different correctness requirements. Why a routing duration is not a delivery promise. ## HERE documentation * [Routing API v8 get started](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/get-started.html) * [Routing API v8 reference](https://docs.here.com/routing/reference/routing-api-v8-calculateroutes) * [Transport modes](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/topics/transport-modes.html) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Distance Matrix Source: https://docs.placematic.com/examples/distance-matrix Matrix Routing v8 — the async job lifecycle, mode-specific size ceilings, and the flat array that transposes silently. # Distance Matrix **Problem:** I need travel times between many origins and many destinations. Not a loop of routing calls. If you are calling the Routing API in a loop to build a table of travel times, this page will pay for itself. A 20×500 problem is 10,000 routing calls, or one matrix request. ## Prerequisites * A HERE API key with Matrix Routing entitlement * `export HERE_API_KEY="..."` ## Size ceilings depend on mode There is no single "HERE matrix size limit." Verified against the [Matrix Routing v8 OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi), v8.47.0: | Mode | Sync | Async | Live traffic + custom options | | ------------------------------------------------------------- | ----------------------- | --------------- | ----------------------------- | | **Flexible** (`world`) | 15×100 or 100×1 | 15×100 or 100×1 | Yes | | **Region** (`circle`, `boundingBox`, `polygon`, `autoCircle`) | 500×500 | 10,000×10,000 | Yes | | **Profile** (`world` + `profile`) | 500×500, 1×2000, 2000×1 | 10,000×10,000 | **No** | **A truck-constrained matrix with live traffic runs in Flexible mode and caps at 15 × 100.** Read the spec before architecting. ## The code — async, which is what you want at scale ```python Python theme={null} import os import time import requests BASE = "https://matrix.router.hereapi.com/v8" API_KEY = os.environ["HERE_API_KEY"] class EntitlementError(Exception): ... def submit_matrix(origins: list[dict], destinations: list[dict], region_center: dict, radius_m: int = 200_000) -> tuple[str, str]: """ Submit an async matrix job. Returns (matrixId, statusUrl). PERSIST BOTH BEFORE POLLING. If your worker restarts mid-poll, the job is still running on HERE's side. Resubmitting bills again. """ body = { "origins": origins, # [{"lat": .., "lng": ..}, ...] "destinations": destinations, "regionDefinition": { # Region mode: max 400km diameter "type": "circle", "center": region_center, "radius": radius_m, }, "matrixAttributes": ["travelTimes", "distances"], } resp = requests.post(f"{BASE}/matrix", params={"apiKey": API_KEY}, json=body, timeout=30) if resp.status_code == 403: raise EntitlementError("Matrix Routing not entitled") resp.raise_for_status() data = resp.json() # Use the statusUrl HERE returns. Do NOT construct it — global load # balancing means a self-built URL may resolve to a different region. return data["matrixId"], data["statusUrl"] def poll_until_done(status_url: str, max_wait_s: int = 600) -> str: """Poll with backoff. Returns the result URL on success.""" backoff, waited = 2.0, 0.0 while waited < max_wait_s: resp = requests.get(status_url, params={"apiKey": API_KEY}, timeout=15, allow_redirects=False) if resp.status_code in (301, 302, 303): return resp.headers["Location"] resp.raise_for_status() status = resp.json().get("status") if status == "completed": return resp.json()["resultUrl"] if status == "failed": raise RuntimeError(f"Matrix failed: {resp.json()}") time.sleep(backoff) waited += backoff backoff = min(backoff * 1.5, 30.0) raise TimeoutError(f"Matrix not complete after {max_wait_s}s") def fetch_result(result_url: str) -> dict: resp = requests.get(result_url, params={"apiKey": API_KEY}, timeout=60) resp.raise_for_status() return resp.json() # --------------------------------------------------------------------------- # THE INDEXING HELPER. Write this once. Unit-test it. Never index inline. # --------------------------------------------------------------------------- def cell(matrix: dict, origin_idx: int, dest_idx: int, attr: str = "travelTimes"): """ Result arrays are FLAT and ROW-MAJOR. A 2x2 returns travelTimes: [73, 1231, 983, 400] [0][0]=73 [0][1]=1231 [1][0]=983 [1][1]=400 Index it wrong and you get a silently transposed matrix. Every travel time is plausible. Every assignment is wrong. Nothing throws. """ n_dest = matrix["numDestinations"] return matrix[attr][origin_idx * n_dest + dest_idx] def cell_error(matrix: dict, origin_idx: int, dest_idx: int) -> int | None: """ Per-cell error codes. Read them. 1 = graph disconnected 2 = waypoint matching failed 3 = route found but VIOLATES A RESTRICTION 4 = waypoint outside region """ codes = matrix.get("errorCodes") if not codes: return None n_dest = matrix["numDestinations"] code = codes[origin_idx * n_dest + dest_idx] return code or None if __name__ == "__main__": depots = [{"lat": 41.8845, "lng": -87.6386}, {"lat": 41.5250, "lng": -88.0817}] stops = [{"lat": 41.9028, "lng": -87.6317}, {"lat": 41.7508, "lng": -87.7713}] matrix_id, status_url = submit_matrix(depots, stops, region_center={"lat": 41.8, "lng": -87.8}) # PERSIST matrix_id AND status_url HERE, before polling. result_url = poll_until_done(status_url) m = fetch_result(result_url)["matrix"] for i in range(m["numOrigins"]): for j in range(m["numDestinations"]): err = cell_error(m, i, j) if err == 3: print(f"depot {i} → stop {j}: route VIOLATES a restriction") elif err: print(f"depot {i} → stop {j}: error {err}") else: print(f"depot {i} → stop {j}: {cell(m, i, j)}s") ``` ```javascript Node.js theme={null} const BASE = "https://matrix.router.hereapi.com/v8"; const API_KEY = process.env.HERE_API_KEY; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); export async function submitMatrix(origins, destinations, regionCenter, radiusM = 200_000) { const resp = await fetch(`${BASE}/matrix?apiKey=${API_KEY}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ origins, destinations, regionDefinition: { type: "circle", center: regionCenter, radius: radiusM }, matrixAttributes: ["travelTimes", "distances"], }), }); if (resp.status === 403) throw new Error("Matrix Routing not entitled"); if (!resp.ok) throw new Error(`HERE returned ${resp.status}`); const { matrixId, statusUrl } = await resp.json(); // Persist BOTH before polling. return { matrixId, statusUrl }; } export async function pollUntilDone(statusUrl, maxWaitMs = 600_000) { let backoff = 2000, waited = 0; while (waited < maxWaitMs) { const resp = await fetch(`${statusUrl}?apiKey=${API_KEY}`, { redirect: "manual" }); if ([301, 302, 303].includes(resp.status)) return resp.headers.get("location"); const body = await resp.json(); if (body.status === "completed") return body.resultUrl; if (body.status === "failed") throw new Error(JSON.stringify(body)); await sleep(backoff); waited += backoff; backoff = Math.min(backoff * 1.5, 30_000); } throw new Error("Matrix timed out"); } /** Flat, row-major. Write once, test once, never inline. */ export function cell(matrix, originIdx, destIdx, attr = "travelTimes") { return matrix[attr][originIdx * matrix.numDestinations + destIdx]; } ``` ## Response walkthrough ```json theme={null} { "matrixId": "bc79a808-dbac-4e49-88f2-27ec66a473ef", "matrix": { "numOrigins": 2, "numDestinations": 2, "travelTimes": [73, 1231, 983, 400], "distances": [10, 109, 10, 30] }, "regionDefinition": { "type": "circle", "center": {...}, "radius": 10000 } } ``` **`travelTimes` is flat and row-major.** `[73, 1231, 983, 400]` for a 2×2 means: dest0 dest1 origin0 73 1231 origin1 983 400 Index as `travelTimes[origin * numDestinations + destination]`. Transpose it and every value is plausible, every assignment is wrong, and nothing throws. This is why matrix bugs survive code review. **`errorCodes`** is a parallel flat array. `3` means a route was found *but it violates a restriction*. Read it. ## Common mistakes **Looping the Routing API.** The reason this page exists. **Indexing the flat array as if it were nested.** Silently transposed. **Constructing the `statusUrl`.** HERE's spec explicitly warns that a self-built URL may resolve to a different region and return `404`. Use the returned one. **Blocking a request thread on an async job.** Submit, persist, return, poll on a worker. **Losing the `matrixId` on restart.** Resubmission is a full recharge. **Architecting against a size ceiling from a comparison table.** Read the spec. **Building a truck matrix in car mode.** Matrix accepts truck parameters. Wrong times, wrong assignments. **Requesting `consumptions` when nothing reads them.** **Ignoring `errorCodes`.** A cell with `3` returned a route that breaks a constraint. ## Production considerations **Persist `matrixId` and `statusUrl` before the first poll.** The job runs on HERE's side whether or not your process remembers it. **Cache on a hash of the input set.** Depot locations do not move. If origins, destinations, and departure time are unchanged, the matrix is unchanged. **Write exactly one indexing helper.** Unit-test it against a hand-computed 2×3. Never index the array anywhere else. This is the highest-value five lines in a matrix integration. **Handle `429` on submission with backoff.** Concurrent job limits exist. **Pass truck parameters for commercial fleets.** Same `vehicle[...]` fields as [Truck Route](/examples/truck-route). **Region mode caps at 400 km diameter.** Beyond that you are in Flexible or Profile mode, with different ceilings and different trade-offs. ## Related Modes, region definitions, and the async lifecycle in full. The loop, why engineers write it, and the decision tree. Where the matrix feeds the solver. Pattern 2: choosing the right primitive. ## HERE documentation * [Matrix Routing v8 OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi) — authoritative on modes, limits, and error codes * [Matrix Routing API v8](https://www.here.com/docs/category/matrix-routing-api-v8) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Calculate an EV Route Source: https://docs.placematic.com/examples/ev-route Electric vehicle routing with HERE Routing v8 — consumption curves, charge state, and charging stops returned as postActions. # Calculate an EV Route **Problem:** Route an electric vehicle to a destination it cannot reach on its current charge. This is not car routing with a range filter. The vehicle's ability to reach charger three depends on whether it stopped at charger one. You cannot approximate it by post-processing a car route against a charger database. ## Prerequisites * A HERE API key with Routing entitlement * `export HERE_API_KEY="..."` **EV routing is a capability within the Routing API**, at the same `router.hereapi.com/v8/routes` endpoint. It is not a separate service. The **HERE EV Charge Points API** — for finding and describing charging infrastructure — is a *different product* with a *separate entitlement*. A key that routes EVs may return `403` from charge points. ## The code ```python Python theme={null} import os import requests HOST = "https://router.hereapi.com/v8/routes" API_KEY = os.environ["HERE_API_KEY"] class EntitlementError(Exception): ... class NoRouteError(Exception): ... # --- Vehicle energy profile: MODEL DATA, not call-site literals ------------- # freeFlowSpeedTable / trafficSpeedTable are speed→consumption CURVES. # Format: "speed_kmh,kWh_per_km, speed_kmh,kWh_per_km, ..." # A linear assumption is optimistic on highways and catastrophically # optimistic on grades. EV_PROFILE = { "ev[freeFlowSpeedTable]": "0,0.239,27,0.239,45,0.259,60,0.196,75,0.207," "90,0.238,100,0.26,110,0.296,120,0.337,130,0.351,250,0.351", "ev[trafficSpeedTable]": "0,0.349,27,0.319,45,0.329,60,0.266,75,0.287," "90,0.318,100,0.33,110,0.335,120,0.35,130,0.36,250,0.36", "ev[auxiliaryConsumption]": 1.8, # kW — climate, refrigeration "ev[ascent]": 9, # Wh per metre of elevation gain "ev[descent]": 4.3, # Wh recuperated per metre of loss # Max charging rate at a given charge level: "kWh,kW, kWh,kW, ..." "ev[chargingCurve]": "0,239,32,199,56,167,60,130,64,111,68,83,72,55,76,33,78,17,80,1", "ev[maxCharge]": 80, # kWh — battery capacity "ev[maxChargeAfterChargingStation]": 72, # do not charge to 100% "ev[minChargeAtChargingStation]": 8, # arrive with a buffer "ev[minChargeAtDestination]": 8, "ev[chargingSetupDuration]": 300, # seconds: park, plug, auth "ev[connectorTypes]": "iec62196Type2Combo,Chademo,Tesla", } def ev_route(origin: str, destination: str, initial_charge_kwh: float) -> dict: """ Route an EV, inserting charging stops if needed. `initialCharge` is an INPUT, not an assumption. A vehicle at 22% produces a materially different route than the same vehicle at 90%. If telematics provides live state of charge, use it. If not, tell the driver the ETA assumes a full battery. """ params = { "transportMode": "car", "origin": origin, "destination": destination, "return": "summary,polyline", # ev[makeReachable]=true lets the engine INSERT charging stops. # Without it, an unreachable destination returns no route. "ev[makeReachable]": "true", "ev[initialCharge]": initial_charge_kwh, "apiKey": API_KEY, **EV_PROFILE, } resp = requests.get(HOST, params=params, timeout=30) if resp.status_code == 403: raise EntitlementError("EV routing not entitled") resp.raise_for_status() body = resp.json() if not body.get("routes"): raise NoRouteError(str(body.get("notice", []))) sections = body["routes"][0]["sections"] # Charging stops appear as `postActions` on a section. # Model them as FIRST-CLASS SCHEDULE EVENTS — a 45-minute stop is # 45 minutes the vehicle is not delivering. stops = [] for i, s in enumerate(sections): for pa in s.get("postActions", []): if pa.get("action") == "charging": stops.append({ "after_section": i, "duration_s": pa.get("duration"), "charge_before": s.get("arrival", {}).get("charge"), "charge_after": pa.get("targetCharge"), }) drive_s = sum(s["summary"]["duration"] for s in sections) charge_s = sum(st["duration_s"] or 0 for st in stops) return { "drive_duration_s": drive_s, "charging_duration_s": charge_s, "total_duration_s": drive_s + charge_s, # ← what the dispatcher needs "charging_stops": stops, "sections": len(sections), } if __name__ == "__main__": r = ev_route("52.533959,13.404780", "51.741505,14.352413", initial_charge_kwh=48) print(f"Drive {r['drive_duration_s']}s + charge {r['charging_duration_s']}s") for st in r["charging_stops"]: print(f" charge {st['duration_s']}s → {st['charge_after']} kWh") ``` ```bash cURL theme={null} # Verified against HERE's EV routing developer guide. # departureTime=any disables traffic-aware routing. curl -X GET \ 'https://router.hereapi.com/v8/routes?\ transportMode=car&\ origin=52.533959,13.404780&\ destination=51.741505,14.352413&\ departureTime=any&\ return=summary&\ ev[makeReachable]=true&\ ev[initialCharge]=48&\ ev[maxCharge]=80&\ ev[maxChargeAfterChargingStation]=72&\ ev[connectorTypes]=iec62196Type2Combo&\ ev[freeFlowSpeedTable]=0,0.239,27,0.239,45,0.259,60,0.196,75,0.207,90,0.238,100,0.26,110,0.296,120,0.337,130,0.351,250,0.351&\ ev[trafficSpeedTable]=0,0.349,27,0.319,45,0.329,60,0.266,75,0.287,90,0.318,100,0.33,110,0.335,120,0.35,130,0.36,250,0.36&\ ev[auxiliaryConsumption]=1.8&\ ev[ascent]=9&\ ev[descent]=4.3&\ ev[chargingCurve]=0,239,32,199,56,167,60,130,64,111,68,83,72,55,76,33,78,17,80,1&\ apikey='"${HERE_API_KEY}" ``` ## Response walkthrough With EV parameters enabled, each section's `departure` and `arrival` blocks carry the battery charge at that point, and charging stops appear as **`postActions`**. ```json theme={null} { "routes": [{ "sections": [{ "departure": { "charge": 48.0, "place": {...} }, "arrival": { "charge": 8.4, "place": {...} }, "summary": { "duration": 4210, "length": 118000 }, "postActions": [ { "action": "charging", "duration": 1980, "targetCharge": 72.0 } ] }, { "departure": { "charge": 72.0 }, "arrival": { "charge": 21.5 } }] }] } ``` **`summary.duration` is drive time only.** The charging stop's `duration` is separate, in `postActions`. A UI that shows `summary.duration` as the ETA has silently omitted 33 minutes of charging. Sum both. ## Key parameters | Parameter | Meaning | | ----------------------------------- | --------------------------------------------------------- | | `ev[initialCharge]` | Charge at departure, kWh. **An input, not an assumption** | | `ev[maxCharge]` | Battery capacity, kWh | | `ev[makeReachable]` | `true` lets the engine insert charging stops | | `ev[freeFlowSpeedTable]` | Speed → consumption curve, free-flow | | `ev[trafficSpeedTable]` | Speed → consumption curve, in traffic | | `ev[chargingCurve]` | Charge level → max charging rate | | `ev[ascent]` / `ev[descent]` | Wh per metre of elevation gain / loss | | `ev[auxiliaryConsumption]` | kW for climate, refrigeration | | `ev[maxChargeAfterChargingStation]` | Charging past \~90% is slow. Cap it | | `ev[connectorTypes]` | Comma-separated. Vehicle-compatible types only | ## Common mistakes **Filtering a car route against a charger table.** **Assuming full charge.** Nearly every real dispatch begins at partial charge. **Omitting `ev[makeReachable]`.** An unreachable destination returns no route rather than a route with a charging stop. **A linear consumption model.** Highways and grades break it. **Ignoring `auxiliaryConsumption`.** Refrigerated vans in July. **Reading only `summary.duration`.** Charging time is in `postActions`. **Hiding the charging stop inside the polyline.** Drivers and dispatchers both need it surfaced. **Routing commercial EVs against passenger charging data.** Different connectors, inaccessible sites. HERE documents EV truck charging as a distinct concern. **Assuming your routing key queries charge points.** Separate entitlement. **Treating charger availability as static.** A confidently routed truck arriving at an occupied charger, with no range to reach an alternative, is worse than no routing. ## Production considerations **Energy profile is model data.** Battery capacity, consumption curve, connector types, charging curve. On the vehicle record. Never at a call site. **Pre-check feasibility cheaply.** Range versus straight-line distance rejects most infeasible assignments before you compute anything. See [Fleet Electrification](/use-cases/fleet-electrification). **Use live state of charge** where telematics provides it. Disclose the assumption where it does not. **Model the charging stop as a schedule event.** The vehicle is unavailable for 33 minutes. **Cache the charger network, not availability.** Stations move slowly. Availability changes by the minute — and availability is a query against the *EV Charge Points API*, a separate product. **Cap `maxChargeAfterChargingStation`.** Charging from 80% to 100% takes disproportionately long. `chargingCurve` encodes this; setting the cap uses it. ## Related Routing versus charge points, and the two-product distinction. Answering feasibility before you buy trucks — usually without this API. The dispatch-time architecture. `range[type]=consumption` — everywhere reachable on this charge. ## HERE documentation * [EV routing in Routing API v8](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/concepts/ev-routing.html) * [Calculate a route with charging](https://docs.here.com/routing/docs/routing-v8-ev-routing) * [HERE EV Charge Points API v3](https://www.here.com/docs/category/ev-charge-points-api-v3) — separate product *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Fleet Dashboard — End to End Source: https://docs.placematic.com/examples/fleet-dashboard Ingest 648,000 GPS packets a day and call HERE roughly 2,400 times. Stop detection, lazy reverse geocoding, truck routing, and ETA refresh without re-routing. # Fleet Dashboard — End to End **Problem:** 200 trucks, positions every ten seconds, a dispatcher watching a map. That is **648,000 packets per day.** The naive dashboard reverse-geocodes each one. **Nothing expensive touches a GPS packet on arrival.** Not geocoding. Not matching. Not routing. Not positioning. The packet is validated and stored. Everything else happens on an event, on view, or overnight. ## The architecture ```mermaid theme={null} flowchart TB subgraph Hot["Ingest — 648k/day, ZERO HERE calls"] PKT[GPS packet] VAL[Validate + dedupe] RAW[(Raw positions)] LAST[Last-known
Redis] end subgraph Events["Event detection — local, free"] STOP[Stop detection
stationary > 4 min] GEO[Geofence transitions
ST_Contains] DEV[Route deviation] end subgraph Lazy["On demand — a human is looking"] RG[/v1/revgeocode
selected vehicle only/] TILES[Raster Tile v3] FLOW[/v7/flow
corridor filter/] end subgraph Events2["On event"] MRG[/v1/multi-revgeocode
12 stops, 1 call/] TR[/v8/routes
truck, on deviation/] end subgraph Night["Overnight — nothing waiting"] SEG[Segment trips
drop idle] RM[/v8/match/routelinks/] end PKT --> VAL --> RAW VAL --> LAST LAST --> STOP --> MRG LAST --> GEO LAST --> DEV --> TR LAST --> RG RAW --> SEG --> RM ``` **Daily HERE call budget for 200 trucks:** | Surface | Calls/day | Why | | ------------------------- | ----------- | ------------------------------------- | | Reverse geocode (stops) | \~200 | 12 stops ÷ `multi-revgeocode` batches | | Reverse geocode (on view) | \~50 | One per vehicle a dispatcher clicks | | Truck routing | \~200 | Once per dispatch, plus deviations | | Traffic flow | \~500 | Corridor queries, cached 60 s | | Route matching | 200 | One per closed trip, overnight | | Tiles | CDN | | | **Total** | **\~1,150** | Against 648,000 packets | **Instrument the ratio of reverse-geocode calls to detected stops.** It should be near 1. That single metric tells you more about your bill than any invoice line item. ## Prerequisites * HERE key with Geocoding & Search, Routing, Traffic, Route Matching, Map Rendering * Redis, PostGIS * `export HERE_API_KEY="..."` ## Ingest — zero API calls ```python theme={null} import redis, psycopg2 r = redis.Redis() STATIONARY_M = 30 # metres STOP_DWELL_S = 240 # 4 minutes def ingest(vehicle_id: str, lat: float, lng: float, ts: int, heading: float): """ Called 648,000 times a day. Does nothing expensive. Validated. Stored. Last-known updated. That is all. """ store_raw(vehicle_id, lat, lng, ts, heading) # append-only r.hset(f"veh:{vehicle_id}", mapping={ "lat": lat, "lng": lng, "ts": ts, "heading": heading, }) # Stop detection is ARITHMETIC. No API. if _stationary(vehicle_id, lat, lng, ts): emit_stop_event(vehicle_id, lat, lng, ts) # ← the unit of work # Geofence containment is ST_Contains. No API. check_geofences(vehicle_id, lat, lng) def _stationary(vehicle_id, lat, lng, ts) -> bool: anchor = r.hgetall(f"anchor:{vehicle_id}") if not anchor: r.hset(f"anchor:{vehicle_id}", mapping={"lat": lat, "lng": lng, "ts": ts}) return False if _haversine_m(lat, lng, float(anchor[b"lat"]), float(anchor[b"lng"])) > STATIONARY_M: r.hset(f"anchor:{vehicle_id}", mapping={"lat": lat, "lng": lng, "ts": ts}) return False return ts - int(anchor[b"ts"]) >= STOP_DWELL_S ``` ## Reverse geocode — on the event, in a batch ```python theme={null} import requests, os MULTI = "https://revgeocode.search.hereapi.com/v1/multi-revgeocode" REV = "https://revgeocode.search.hereapi.com/v1/revgeocode" API_KEY = os.environ["HERE_API_KEY"] COORD_DP = 5 # ~1 m. GPS jitter defeats an exact-match cache. _cache: dict[str, str] = {} def label_stops(stops: list[dict]) -> list[dict]: """ Twelve detected stops. ONE request. Looping /revgeocode over a list is the same mistake as looping routing to build a matrix. """ uncached = [s for s in stops if _key(s) not in _cache] if uncached: r = requests.post(MULTI, params={"apiKey": API_KEY}, json={ "items": [{"id": s["id"], "at": f"{s['lat']},{s['lng']}"} for s in uncached] }, timeout=30) if r.status_code == 403: raise RuntimeError("multi-revgeocode not entitled") r.raise_for_status() for item in r.json().get("items", []): _cache[_key_from_id(item["id"], uncached)] = item.get("title") return [{**s, "address": _cache.get(_key(s))} for s in stops] def label_on_view(vehicle_id: str) -> str | None: """ A dispatcher clicked ONE vehicle. Resolve ONE address. Do not pre-resolve the other 199. """ v = r.hgetall(f"veh:{vehicle_id}") lat, lng = float(v[b"lat"]), float(v[b"lng"]) k = f"{lat:.{COORD_DP}f},{lng:.{COORD_DP}f}" if k in _cache: return _cache[k] resp = requests.get(REV, params={ "at": f"{lat},{lng}", "limit": 1, "apiKey": API_KEY, }, timeout=10) resp.raise_for_status() items = resp.json().get("items", []) if not items: return None _cache[k] = items[0]["address"]["label"] return _cache[k] def _key(s): return f"{s['lat']:.{COORD_DP}f},{s['lng']:.{COORD_DP}f}" ``` A returned address is a **nearest match**, not a truth. In rural areas it may be several hundred metres away, on a different property. Store the original coordinate alongside it. Provide `heading` where telematics supplies it. On a divided highway, a coordinate 15 m from the centreline resolves to either carriageway. Heading disambiguates — free accuracy, fewer "the truck is on the wrong side of the interstate" tickets. ## Truck routing — constraints live on the vehicle record ```python theme={null} from dataclasses import dataclass ROUTER = "https://router.hereapi.com/v8/routes" @dataclass(frozen=True) class TruckProfile: """ Model data. NEVER hardcoded at a call site. A refactor that drops one of these is invisible in code review and returns 200. """ height_cm: int # centimetres. 410 = 4.1 m gross_weight_kg: int # kilograms axle_count: int # base vehicle + ALL trailers def dispatch_route(origin: str, dest: str, p: TruckProfile) -> dict: if not 100 <= p.height_cm <= 500: raise ValueError(f"height_cm={p.height_cm} — metres, not centimetres?") r = requests.get(ROUTER, params={ "transportMode": "truck", "origin": origin, "destination": dest, "vehicle[height]": p.height_cm, # `vehicle` supersedes `truck` "vehicle[grossWeight]": p.gross_weight_kg, "vehicle[axleCount]": p.axle_count, "return": "summary,polyline", "departureTime": "now", # traffic at DEPARTURE, not now "apiKey": API_KEY, }, timeout=20) if r.status_code == 403: raise RuntimeError("Truck routing not entitled") r.raise_for_status() body = r.json() if not body.get("routes"): # No LEGAL path. Escalate to a dispatcher. # Do NOT fall back to car routing. raise NoLegalRouteError(body.get("notice")) s = body["routes"][0]["sections"][0] return {"duration_s": s["summary"]["duration"], "polyline": s["polyline"]} ``` **Push the profile to the driver's device.** The most dangerous pattern in mobile fleet software: server-side truck routing with constraints, device-side recalculation without them. The driver deviates, the phone reroutes, and the constraint set never left your backend. ## ETA refresh — without re-routing ```python theme={null} from flexpolyline import decode def refresh_eta(vehicle_id: str, planned_polyline: str, planned_duration_s: int) -> dict: """ Recompute remaining duration from GEOMETRY and current position. Arithmetic on data you already hold. Zero API calls. Re-routing on every GPS ping to refresh an ETA is the reverse-geocode-every-ping mistake wearing a different hat. """ v = r.hgetall(f"veh:{vehicle_id}") lat, lng = float(v[b"lat"]), float(v[b"lng"]) coords = decode(planned_polyline).polyline idx, off_route_m = _nearest_vertex(coords, lat, lng) if off_route_m > DEVIATION_THRESHOLD_M: return {"deviated": True} # ← NOW call routing. Only now. remaining_frac = 1.0 - (idx / max(len(coords) - 1, 1)) return { "deviated": False, "eta_s": int(planned_duration_s * remaining_frac), } ``` **Routing duration is not the delivery promise.** It omits preparation time, service time, and dwell. Model those separately, from your own history. See [ETA Calculation](/use-cases/eta-calculation). ## Traffic overlay — corridor, not bounding box ```python theme={null} FLOW = "https://data.traffic.hereapi.com/v7/flow" def route_traffic(planned_polyline: str) -> list[dict]: """ `corridor:` accepts a Flexible Polyline + radius. For "what is the traffic on this route," a corridor is dramatically cheaper and more relevant than a bbox containing the whole city. """ r = requests.get(FLOW, params={ "in": f"corridor:{planned_polyline};r=50", "locationReferencing": "none", # MANDATORY. `none` = no geometry back. "minJamFactor": 4.0, # server-side filter "apiKey": API_KEY, }, timeout=15) r.raise_for_status() return [{"street": i["location"].get("description"), "jam_factor": i["currentFlow"]["jamFactor"]} # 0 free … 10 closed for i in r.json().get("results", [])] ``` `locationReferencing` is mandatory. Omitting it returns `E608051`. **Traffic is a separate meter.** An overlay left permanently enabled is a cost decision, not a styling one. ## Overnight — trip matching ```python theme={null} MATCH = "https://routematching.hereapi.com/v8/match/routelinks" def match_closed_trip(trip_id: str) -> dict: """ Batch. Nothing is waiting. Latency is worth nothing here. Segment and downsample BEFORE submission — in real feeds this removes a substantial fraction of points at no information cost. """ points = segment_and_downsample(load_raw_trace(trip_id)) r = requests.post(MATCH, params={ "routeMatch": 1, "mode": "fastest;truck;traffic:disabled", "legal": "access,oneway,thrutraf,turn", "apiKey": API_KEY, }, data=to_csv(points), headers={"Content-Type": "text/plain"}, timeout=70) # HERE cancels around 50 s of compute r.raise_for_status() body = r.json() # THE audit artifact. Anything less is an assertion. return { "raw_trace": points, # immutable source "route_links": body["RouteLinks"], # segments actually driven "map_version": body["MapVersion"], # ← cite this in any dispute "matched_at": int(time.time()), "low_confidence": [tp for tp in body["TracePoints"] if tp.get("confidenceValue", 0) < 0.6], } ``` [Reverse geocoding](/examples/reverse-geocode) cannot reconstruct a driven route. It returns an address, not a road segment, and it will not survive an IFTA audit. `MapVersion` is a real response field. A trip matched in March against a March map may match differently in September. Both can be correct. Record which map produced which result. ## Common mistakes **Reverse-geocoding on packet ingest.** The dominant cost error in fleet software. **Looping `/revgeocode` over detected stops.** `multi-revgeocode` exists. **Re-routing on every ping to refresh an ETA.** **Constraints hardcoded at call sites.** They belong on the vehicle record. **Constraints on the server, absent on the device.** **Passing metres where centimetres are expected.** A four-centimetre truck routes anywhere, and returns `200`. **Falling back to car routing when truck routing finds no path.** Worse than failing. **Using reverse geocoding to determine which road the vehicle drove.** **Using reverse geocoding for geofence containment.** `ST_Contains`. **Polling a city-wide bbox for traffic.** Use `corridor:`. **Omitting `locationReferencing`.** **Matching packets instead of trips.** **Not recording `MapVersion`.** **Positioning API for outdoor road vehicles.** They have GPS. It is free and better. ## Scaling **GPS volume is the quiet problem.** Anything per-point multiplies by fleet size × ping rate. 200 trucks at 10 s is 648,000/day. At 1,000 trucks on 5 s, it is 6.5 million. **Design so that nothing expensive touches a raw ping.** **Evaluate asset-based pricing.** A countable vehicle population with unpredictable call volume is exactly its shape. A dispatcher who reroutes forty times a day is punished by call-volume pricing for diligence. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Related The ingest tier, in full. Planning, execution, reconstruction. HOS coupling, IFTA, and the audit artifact. `MapVersion`, confidence, and warnings. ## HERE documentation * [Routing API v8](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/get-started.html) * [Geocoding & Search v7](https://docs.here.com/geocoding-and-search/docs/introduction-to-here-geocoding-search-api-v7) * [Traffic API v7](https://docs.here.com/traffic-api/docs/introduction-to-here-traffic-api-v7) * [Route Matching v8](https://www.here.com/docs/bundle/route-matching-api-developer-guide/page/README.html) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. We have deployed HERE into production ELD systems. [Talk to us](https://placematic.com/contact/). # Geocode an Address Source: https://docs.placematic.com/examples/geocode-address Convert an address to coordinates with HERE Geocoding & Search v7 — including confidence scoring, the routing access point, and structured queries. # Geocode an Address **Problem:** I have an address string. I need coordinates, and I need to know whether to trust them. ## Prerequisites * A HERE API key with Geocoding & Search entitlement * `export HERE_API_KEY="..."` Geocoding & Search v7 uses **per-endpoint hosts**. Forward geocoding is `geocode.search.hereapi.com`, not a shared base URL. This surprises teams building a single HTTP client. ## The code ```python Python theme={null} import os import time import requests HOST = "https://geocode.search.hereapi.com/v1/geocode" API_KEY = os.environ["HERE_API_KEY"] # Below this, treat the result as unverified and route to review. # Calibrate against your own ground truth — do not port this number. MIN_QUERY_SCORE = 0.90 class EntitlementError(Exception): """403 — valid key, missing product entitlement. Never retry.""" def geocode(address: str, country: str | None = None, retries: int = 3) -> dict | None: """ Forward geocode a free-text address. Returns a normalized record, or None if no acceptable match was found. Raises EntitlementError on 403 — retrying will never succeed. """ params = {"q": address, "apiKey": API_KEY, "limit": 1} if country: # ISO 3166-1 alpha-3. Narrows results and reduces ambiguity. params["in"] = f"countryCode:{country}" backoff = 1.0 for attempt in range(retries): resp = requests.get(HOST, params=params, timeout=10) if resp.status_code == 403: raise EntitlementError("Key valid, geocoding not entitled") if resp.status_code == 429: time.sleep(backoff) backoff *= 2 continue if resp.status_code >= 500: time.sleep(backoff) backoff *= 2 continue resp.raise_for_status() break else: raise RuntimeError(f"Geocode failed after {retries} attempts") items = resp.json().get("items", []) if not items: return None # No match. Not an error. item = items[0] scoring = item.get("scoring", {}) return { "position": item["position"], # The address location "access": (item.get("access") or [None])[0], # Road-network entry point "label": item["address"]["label"], # Normalized address "result_type": item["resultType"], # "PA" = point address (surveyed). "interpolated" = estimated on a segment. "house_number_type": item.get("houseNumberType"), "query_score": scoring.get("queryScore"), "field_score": scoring.get("fieldScore", {}), "here_id": item["id"], "trusted": scoring.get("queryScore", 0) >= MIN_QUERY_SCORE, } if __name__ == "__main__": result = geocode("425 W Randolph St, Chicago, IL", country="USA") if result is None: print("No match — route to exception queue") elif not result["trusted"]: print(f"Low confidence {result['query_score']} — review: {result['field_score']}") else: # Route to `access`, not `position`. See below. print(result["access"] or result["position"]) ``` ```javascript Node.js theme={null} const HOST = "https://geocode.search.hereapi.com/v1/geocode"; const API_KEY = process.env.HERE_API_KEY; const MIN_QUERY_SCORE = 0.9; class EntitlementError extends Error {} const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); /** * Forward geocode a free-text address. * @returns {Promise} null when no acceptable match exists. */ export async function geocode(address, { country, retries = 3 } = {}) { const params = new URLSearchParams({ q: address, apiKey: API_KEY, limit: "1" }); if (country) params.set("in", `countryCode:${country}`); let backoff = 1000; let resp; for (let attempt = 0; attempt < retries; attempt++) { const ctrl = new AbortController(); const timer = setTimeout(() => ctrl.abort(), 10_000); try { resp = await fetch(`${HOST}?${params}`, { signal: ctrl.signal }); } finally { clearTimeout(timer); } if (resp.status === 403) throw new EntitlementError("Geocoding not entitled"); if (resp.status === 429 || resp.status >= 500) { await sleep(backoff + Math.random() * 250); // jitter backoff *= 2; continue; } if (!resp.ok) throw new Error(`HERE returned ${resp.status}`); break; } const { items = [] } = await resp.json(); if (items.length === 0) return null; const item = items[0]; const scoring = item.scoring ?? {}; return { position: item.position, access: item.access?.[0] ?? null, label: item.address.label, resultType: item.resultType, houseNumberType: item.houseNumberType, queryScore: scoring.queryScore, fieldScore: scoring.fieldScore ?? {}, hereId: item.id, trusted: (scoring.queryScore ?? 0) >= MIN_QUERY_SCORE, }; } ``` ```bash cURL theme={null} # Free-text query curl -G 'https://geocode.search.hereapi.com/v1/geocode' \ --data-urlencode 'q=425 W Randolph St, Chicago, IL' \ --data-urlencode 'in=countryCode:USA' \ --data-urlencode 'limit=1' \ --data-urlencode "apiKey=${HERE_API_KEY}" # Structured query — better for validating user input. # Note: use qq OR q, never both. curl -G 'https://geocode.search.hereapi.com/v1/geocode' \ --data-urlencode 'qq=street=W Randolph St;houseNumber=425;city=Chicago;state=IL;country=USA' \ --data-urlencode "apiKey=${HERE_API_KEY}" ``` ## Response walkthrough Abbreviated. Full schema: [HERE geocode documentation](https://docs.here.com/geocoding-and-search/docs/geocode). ```json theme={null} { "items": [{ "title": "425 W Randolph St, Chicago, IL 60606-1506, United States", "id": "here:af:streetsection:hdZ6xBRUraY46IQZCqZidD:...", "resultType": "houseNumber", "houseNumberType": "PA", "address": { "label": "...", "countryCode": "USA", "postalCode": "60606-1506" }, "position": { "lat": 41.88432, "lng": -87.63877 }, "access": [{ "lat": 41.88449, "lng": -87.63877 }], "scoring": { "queryScore": 0.99, "fieldScore": { "city": 1.0, "streets": [0.9], "houseNumber": 1.0 } } }] } ``` **Four fields decide whether this result is safe to store.** **`position` vs `access`.** `position` is the address. `access` is the point on the road network where a vehicle arrives. **Route to `access`.** Routing to `position` may target the geometric centre of a building, or a point separated from the road by a car park. **`houseNumberType`.** `PA` is a surveyed point address. `interpolated` means the position was estimated along a street segment and may be tens of metres off. For last-mile delivery that is the neighbour's door. **`queryScore`.** Overall match quality, 0–1. **`fieldScore`.** *Which component* was uncertain. `queryScore: 0.95` with `fieldScore.houseNumber: 0.4` matched the street confidently and the house number badly. That is a different operational risk from `fieldScore.postalCode: 0.8`. **Store `fieldScore` and `houseNumberType`.** A coordinate persisted without them is a probabilistic estimate that every downstream system will treat as a fact. Low-confidence fallbacks silently corrupt revenue maps and route vehicles to centroids. ## Common mistakes **Sending `q` and `qq` together.** HERE's own guidance notes this produces inconsistent `queryScore`. Pick one. **Using `/geocode` for place search.** "Find restaurants near me" is [`/discover`](/examples/search-poi). **Routing to `position` instead of `access`.** **Discarding `fieldScore`.** The most damaging thing you can do with a geocoding result. **Porting a confidence threshold from another platform.** `queryScore: 0.9` does not mean what Google's `partial_match: false` means. Recalibrate against your own ground truth. **Treating an empty `items` array as an error.** No match is a valid outcome. Route it to an exception queue. **Not normalizing before the call.** `123 Main St` and `123 Main Street` are the same address and two cache misses. ## Production considerations **Cache permanently.** Buildings are stationary. Key on the *normalized* address. Store coordinates, `access`, `queryScore`, `fieldScore`, `houseNumberType`, and `geocoded_at`. This is the single largest cost lever available. See [Caching Geocoding Results](/architecture/caching-geocoding-results). **Do not use a TTL.** A thirty-day expiry invalidates a stable rooftop match for a building that has stood since 1904, and does nothing about the subdivision that opened yesterday. Invalidate on map release, low confidence, correction, or failed delivery. **Threshold and queue.** Results below your calibrated confidence go to a human, not to the database. **Batch anything latency-tolerant.** If the result is written to a database rather than rendered to a screen, use [Batch Geocoding](/examples/batch-geocoding). **Check your contract's storage terms.** Retention of geocoded coordinates is a contract term, not an engineering decision. ## Related Endpoint selection, autocomplete vs autosuggest, confidence scores. Normalization, invalidation, and the privacy question. Why a geocoder and a validation service answer different questions. Four million addresses, once. ## HERE documentation * [Geocode endpoint](https://docs.here.com/geocoding-and-search/docs/geocode) * [Spatial references](https://docs.here.com/geocoding-and-search/docs/code-geocode-spatial-reference) * [Geocoding & Search v7 introduction](https://docs.here.com/geocoding-and-search/docs/introduction-to-here-geocoding-search-api-v7) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Migrating One Endpoint from Google Maps Source: https://docs.placematic.com/examples/google-migration A real geocoding migration, request by request. The provider facade, the semantic differences that return 200, and shadow comparison against ground truth. # Migrating One Endpoint from Google Maps **Problem:** We geocode 400,000 addresses a month against Google. Move it to HERE without breaking anything. **Optimize on Google first.** Cache, deduplicate, debounce. Re-measure. A meaningful share of teams find the bill halves without a vendor change. What remains is your real migration case — and now you have a clean baseline. Migrating an uncached implementation moves the waste to a cheaper meter. You will save money and still be wrong. ## Why geocoding first Six surfaces, migrated in order. Never two at once. | Order | Surface | Why here | | ----- | ---------------------------- | ------------------------------------------ | | **1** | **Batch geocoding** | No UI. If it breaks, you fix a nightly job | | 2 | Telematics reverse geocoding | Backend, high volume | | 3 | Matrix | Where the routing loops were | | 4 | Routing | Now user-visible. Quality gate required | | 5 | Autocomplete | User-facing. Be honest about quality | | 6 | Rendering | Most visible. Tile aesthetics differ | Smallest blast radius, largest saving. Start here. ## Step 1 — the facade, shipped against Google Introduce it **before** HERE exists in the codebase. ```python theme={null} from dataclasses import dataclass from typing import Protocol @dataclass(frozen=True) class GeocodeResult: """ OUR type. Not Google's response shape. Not HERE's. Design the interface from HERE's capability set, not Google's. An interface derived from Google has no field for `access` or `field_score`. When you add HERE, that data has nowhere to live and gets silently dropped. """ lat: float lng: float access_lat: float | None # road-network entry point. Google has none. access_lng: float | None normalized_address: str confidence: float # 0..1 field_confidence: dict # per-component. Google has no equivalent. is_rooftop: bool provider: str # for shadow comparison class GeocodeProvider(Protocol): def geocode(self, address: str, country: str | None) -> GeocodeResult | None: ... ``` **This is where migrations fail.** If your Google implementation had no notion of a road-network access point, an interface derived from it has no field for one. Design from the richer capability set. ## Step 2 — the two adapters, side by side ```python Google adapter theme={null} import requests GOOGLE = "https://maps.googleapis.com/maps/api/geocode/json" class GoogleGeocoder: def geocode(self, address: str, country: str | None = None): params = {"address": address, "key": self.key} if country: params["components"] = f"country:{country}" r = requests.get(GOOGLE, params=params, timeout=10) r.raise_for_status() body = r.json() # Google signals failure in `status`, not HTTP. if body["status"] == "ZERO_RESULTS": return None if body["status"] != "OK": raise RuntimeError(body["status"]) res = body["results"][0] loc = res["geometry"]["location"] lt = res["geometry"]["location_type"] return GeocodeResult( lat=loc["lat"], lng=loc["lng"], access_lat=None, access_lng=None, # no equivalent normalized_address=res["formatted_address"], confidence=0.0 if res.get("partial_match") else 1.0, # coarse field_confidence={}, # no equivalent is_rooftop=(lt == "ROOFTOP"), provider="google", ) ``` ```python HERE adapter theme={null} import requests HERE = "https://geocode.search.hereapi.com/v1/geocode" class HereGeocoder: def geocode(self, address: str, country: str | None = None): params = {"q": address, "limit": 1, "apiKey": self.key} if country: params["in"] = f"countryCode:{country}" # ISO 3166-1 ALPHA-3 r = requests.get(HERE, params=params, timeout=10) # 403 = valid key, MISSING ENTITLEMENT. Never retry. if r.status_code == 403: raise EntitlementError("Geocoding not entitled") r.raise_for_status() items = r.json().get("items", []) if not items: return None # empty items = no match. Not an error. it = items[0] sc = it.get("scoring", {}) acc = (it.get("access") or [None])[0] return GeocodeResult( lat=it["position"]["lat"], lng=it["position"]["lng"], access_lat=acc["lat"] if acc else None, access_lng=acc["lng"] if acc else None, normalized_address=it["address"]["label"], confidence=sc.get("queryScore", 0.0), field_confidence=sc.get("fieldScore", {}), # ← richer than Google is_rooftop=(it.get("houseNumberType") == "PA"), provider="here", ) ``` ## Step 3 — request mapping | Google | HERE | Note | | ------------------------ | ---------------------------------------- | ------------------------------------------------ | | `address=` | `q=` | Free text | | — | `qq=street=…;city=…` | Structured. **Never send `q` and `qq` together** | | `components=country:US` | `in=countryCode:USA` | **alpha-2 → alpha-3** | | `key=` | `apiKey=` | | | `status: "ZERO_RESULTS"` | `items: []` | Different failure signal | | `location_type: ROOFTOP` | `houseNumberType: "PA"` | `interpolated` is the other value | | `partial_match` | `queryScore` + `fieldScore` | Richer. **Rebuild your quality logic** | | `geometry.location` | `position` | The address | | — | `access` | **Road-network entry point. Route to this** | | Geocoding API (batch) | `batch.search.hereapi.com/v7/batch/jobs` | Job lifecycle, not a bulk endpoint | ## Step 4 — semantic differences that return `200` These are the ones that survive code review. **Failure is signalled differently.** Google returns HTTP `200` with `status: "ZERO_RESULTS"`. HERE returns HTTP `200` with `items: []`. Neither raises. **Country codes.** Google: ISO 3166-1 **alpha-2** (`US`). HERE: **alpha-3** (`USA`). A naive port silently filters to nothing. **Two coordinates per result.** HERE returns `position` (the address) *and* `access` (where a vehicle arrives). Google returns one. **Route to `access`.** Routing to `position` may target the geometric centre of a building, or a point separated from the road by a car park. **Confidence is not comparable.** `queryScore: 0.9` does not mean what `partial_match: false` means. **Do not port your threshold.** Recalibrate against your own ground truth before cutover. Plot correctness against `queryScore` and find your own knee. **`fieldScore` has no Google equivalent.** `queryScore: 0.95` with `fieldScore.houseNumber: 0.4` matched the street confidently and the house number badly. That is a failed delivery. `fieldScore.postalCode: 0.8` is not. A single aggregate number cannot express this. **Batch is a job lifecycle.** Create → start → poll → fetch errors → fetch results → delete. `429` on start = concurrency limit, queue it. `204` on `/errors` = **zero errors**, not a failure. `404` on `/result` = not ready yet. **`403` ≠ `401`.** `401` is a bad key. `403` is a valid key without entitlement. Retrying `403` is patient, well-engineered, and permanently futile. ## Step 5 — shadow write ```python theme={null} import asyncio async def geocode_with_shadow(address: str) -> GeocodeResult: """ Primary serves the user. Shadow result is DISCARDED. If the shadow can affect production latency, you have built a second point of failure to test the first. """ primary = PROVIDERS[flags.get("geocode_provider")] # per ENDPOINT result = primary.geocode(address) if flags.get("geocode_shadow_enabled"): asyncio.create_task(_shadow(address, result)) # fire and forget return result async def _shadow(address, primary_result): try: shadow = SHADOW_PROVIDER.geocode(address) await log_comparison(address, primary_result, shadow) except Exception: pass # a shadow failure is not a production failure ``` **The flag is per endpoint, not per user.** A percentage rollout across a geocoder gives you two populations with different data quality and no way to attribute an incident. A rollback should be one flag and one surface. ## Step 6 — compare against ground truth, not against each other Two wrong answers can agree. ```sql theme={null} -- 100 addresses with SURVEYED coordinates. Site visits, or driver-confirmed -- delivery GPS. Not a curated list. Include rural, apartments, and -- addresses that have previously failed delivery. SELECT provider, count(*) AS matched, avg((is_rooftop)::int) AS rooftop_fraction, percentile_cont(0.50) WITHIN GROUP (ORDER BY error_m) AS p50_error_m, percentile_cont(0.90) WITHIN GROUP (ORDER BY error_m) AS p90_error_m, percentile_cont(0.99) WITHIN GROUP (ORDER BY error_m) AS p99_error_m FROM comparison JOIN ground_truth USING (address) GROUP BY provider; ``` **Report the distribution, not the mean.** p99 is where failed deliveries live. And measure **confidence calibration**: among results the platform scored above 0.9, what fraction were actually correct? A geocoder with a *lower* match rate and well-calibrated confidence is more useful — it lets you route uncertain records to an exception queue. Poorly calibrated confidence silently corrupts your database. ## Step 7 — rollback, tested before you need it ```python theme={null} FLAGS = { "geocode_provider": "google", # flip this. One line. No deploy. "geocode_shadow_enabled": True, } ``` **A migration you cannot reverse is not a migration. It is a bet.** Flip it in production, on a Tuesday, deliberately. Verify traffic moves. Flip it back. An untested rollback is a hypothesis. **Store provider-specific IDs in nullable side columns.** If cutting over wrote a `here_id` onto a core record, rollback must not require unwinding it. **Keep both live for one full business cycle.** Two invoices for a month is cheaper than one incident. You are not testing the happy path — you are waiting for the edge case that only occurs on the last Friday of the month. ## Common mistakes **Migrating before optimizing.** **Designing the facade from Google's capabilities.** Truck constraints and `access` points have nowhere to live. **Porting the confidence threshold.** **Country code alpha-2 → alpha-3.** Silent empty results. **Comparing providers against each other.** **Comparing means, not distributions.** **Shadow calls on the response path.** **Percentage rollout by user.** **Migrating two surfaces at once.** Ambiguous root cause on the first incident. **Untested rollback.** **Retrying `403`.** **Treating Batch API as a bulk endpoint.** **Revoking the old keys before a full business cycle completes.** **Presenting savings without migration engineering cost.** Your CFO will ask. ## Related Dual-running, shadow comparison, rollback mechanics. Optimize first. You may not need to migrate. Where each genuinely wins, and how to benchmark. The one-million-address backfill. ## HERE documentation * [Geocode endpoint](https://docs.here.com/geocoding-and-search/docs/geocode) * [Batch API v7](https://docs.here.com/geocoding-and-search/docs/geocoding) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Examples Source: https://docs.placematic.com/examples/index Copy-paste examples for HERE Location Services. Production quality, verified endpoints, no toy code. # Examples Copy an example. Understand it in five minutes. Adapt it to production. Every code block here calls a **verified official HERE endpoint** with parameters checked against HERE's current specification. Where we could not verify something, we omitted the example rather than inventing it. ## How to use this section **These pages explain the code, not the concept.** Why truck routing needs explicit dimensions belongs in [Truck Routing](/guides/truck-routing). How to send the request belongs here. **Parameter reference belongs to HERE.** We link to it. We do not reproduce it, because it changes and a stale copy is worse than no copy. **Nothing here is a starter template.** Every example includes timeouts, error handling that distinguishes `403` from `429`, and the validation that prevents a route no vehicle can drive. Toy code that omits these teaches the wrong lesson at the moment someone copies it. Two things every example assumes, and you should verify before running any of them: **Entitlement.** A key valid for geocoding may return `403` on tour planning. That is a licensing boundary, not a bug. See [Getting a HERE API Key](/getting-started/getting-a-here-api-key). **Units.** HERE's `height` is centimetres. `grossWeight` is kilograms. Pass `4.1` for a 4.1-metre vehicle and you have declared a four-centimetre truck. It will route under every bridge in North America and return `200`. ## Setup Every example reads the key from the environment. Never hardcode it, never put it in a query string in production code — URLs are logged by proxies, load balancers, and CDNs. ```bash theme={null} export HERE_API_KEY="your-key-here" ``` HERE's REST APIs accept the API key as an `apiKey` query parameter, and most also accept an OAuth 2.0 Bearer token. The examples use `apiKey` because it is what HERE's own specification documents for these endpoints. For production, consider proxying through your own backend so the key is never exposed and you can enforce per-tenant quota. See [Multi-Tenant Location Platform](/architecture/multi-tenant-location-platform). ## Error handling, once Every example handles these the same way. We will not repeat the explanation on each page. | Status | Meaning | Retry? | | ---------------------- | ---------------------------------- | ------------------------------------ | | `401` | Key missing, malformed, or revoked | No | | `403` | Key valid, **entitlement missing** | **Never** | | `429` | Rate limit exceeded | Yes, exponential backoff with jitter | | `400` | Malformed request | No — fix the parameters | | `5xx` | Upstream failure | Yes, backoff + circuit breaker | | `200` + empty `routes` | **No path exists** | **No** — this may be correct | `200` with `"routes": []` and a `notice` containing `routeCalculationFailed` is a valid HERE response. Checking `resp.ok` swallows it. For truck and hazmat routing, "no legal route" is a real and correct outcome. A system that retries with relaxed constraints has just produced an illegal route. Retrying `403` is patient, well-engineered, and permanently futile. It generates support tickets. ## The examples Address → coordinates, with confidence scoring and the access point. Coordinates → address. Single and batch, with the telematics pattern. Debounced type-ahead. The one that bills per keystroke if you get it wrong. `/discover` vs `/browse` — relevance ranking vs distance ranking. The baseline. `departureTime`, `return` fields, and empty-routes handling. Constraints, units, and CI assertions against trap geometry. Async job lifecycle, and the flat array that transposes silently. The job API. `204` is not an error. More examples follow: EV routing, isolines, route matching, tour planning, traffic, map rendering, and two end-to-end recipes. ## Related What the APIs do and when to use them. Cost, caching, and the decisions that matter more than the API call. ## HERE documentation * [HERE Technologies documentation](https://www.here.com/docs) * [Identity and Access Management](https://www.here.com/docs) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Nearest Store Search Source: https://docs.placematic.com/examples/nearest-store-search An end-to-end recipe: PostGIS shortlist, one matrix call to rank by drive time, and why straight-line distance is a bad ranker and an excellent filter. # Nearest Store Search **Problem:** A customer enters an address. Show them the nearest store — by drive time, not by circle. A store 800 metres away across a river, a rail line, or a limited-access highway is **not close.** Ranking by straight-line distance is the defect that gets store locators rebuilt. ## The pattern Two stages. The first is free. Geocode the customer address → 1 API call (usually a cache hit) ST\_DWithin: k nearest by distance → 0 API calls. PostGIS. Matrix: origin × k, drive time → 1 API call Sort by travel time → 0 API calls **Straight-line distance is a bad ranker and an excellent filter.** It narrows 2,000 stores to 10 for free. Matrix ranks those 10 correctly. Cost is bounded by `k`, not by store count. A 5,000-store network and a 50-store network cost the same per lookup. ## Prerequisites * HERE API key with Geocoding & Search and Matrix Routing entitlement * PostGIS with your stores in an indexed `geography` column * `export HERE_API_KEY="..."` ## The code ```python Python theme={null} import os import time import requests import psycopg2 GEOCODE = "https://geocode.search.hereapi.com/v1/geocode" MATRIX = "https://matrix.router.hereapi.com/v8" API_KEY = os.environ["HERE_API_KEY"] K = 10 # shortlist size. Bounds the matrix, and therefore the bill. # --- Stage 1: geocode the customer. Cached permanently. ------------------- _geo_cache: dict[str, dict] = {} def geocode_customer(address: str) -> dict: """Buildings are stationary. Key on the NORMALIZED address.""" key = normalize(address) if key in _geo_cache: return _geo_cache[key] resp = requests.get(GEOCODE, params={ "q": address, "limit": 1, "apiKey": API_KEY, }, timeout=10) if resp.status_code == 403: raise RuntimeError("Geocoding not entitled") resp.raise_for_status() items = resp.json().get("items", []) if not items: raise ValueError("Address not found") it = items[0] # `access` is the road-network entry point. Route to this, not `position`. pos = (it.get("access") or [it["position"]])[0] _geo_cache[key] = pos return pos def normalize(addr: str) -> str: return " ".join(addr.lower().replace(",", " ").split()) # --- Stage 2: PostGIS shortlist. ZERO API calls. -------------------------- def shortlist(conn, lat: float, lng: float, k: int = K) -> list[dict]: """ k nearest stores by straight-line distance. Sub-millisecond against a GIST index. Indifferent to your traffic. This step exists to make stage 3 cheap. """ with conn.cursor() as cur: cur.execute(""" SELECT id, name, ST_Y(geom::geometry), ST_X(geom::geometry) FROM stores WHERE open_now ORDER BY geom <-> ST_SetSRID(ST_MakePoint(%s, %s), 4326)::geography LIMIT %s """, (lng, lat, k)) # note: MakePoint is (lng, lat) return [ {"id": r[0], "name": r[1], "lat": r[2], "lng": r[3]} for r in cur.fetchall() ] # --- Stage 3: ONE matrix call. Not k routing calls. ----------------------- def rank_by_drive_time(origin: dict, stores: list[dict]) -> list[dict]: """ 1 origin × k destinations = ONE matrix request. k routing calls would be k requests. At store-locator traffic, that ratio is your entire cost structure. """ body = { "origins": [{"lat": origin["lat"], "lng": origin["lng"]}], "destinations": [{"lat": s["lat"], "lng": s["lng"]} for s in stores], "regionDefinition": { # Region mode. Max 400km diameter. 1×10 is well within sync limits. "type": "circle", "center": {"lat": origin["lat"], "lng": origin["lng"]}, "radius": 100_000, }, "matrixAttributes": ["travelTimes", "distances"], } resp = requests.post(f"{MATRIX}/matrix", params={"apiKey": API_KEY, "async": "false"}, json=body, timeout=30) if resp.status_code == 403: raise RuntimeError("Matrix Routing not entitled") resp.raise_for_status() m = resp.json()["matrix"] n_dest = m["numDestinations"] ranked = [] for j, store in enumerate(stores): # Flat, row-major. origin index is 0. Write the helper once. idx = 0 * n_dest + j err = (m.get("errorCodes") or [0] * n_dest)[idx] if err: continue # 1=disconnected 2=no match 3=violates restriction ranked.append({ **store, "travel_time_s": m["travelTimes"][idx], "distance_m": m["distances"][idx], }) return sorted(ranked, key=lambda s: s["travel_time_s"]) # --- Put together --------------------------------------------------------- def nearest_stores(conn, address: str) -> list[dict]: customer = geocode_customer(address) candidates = shortlist(conn, customer["lat"], customer["lng"]) if not candidates: return [] # Do NOT silently expand the radius return rank_by_drive_time(customer, candidates) if __name__ == "__main__": conn = psycopg2.connect(os.environ["DATABASE_URL"]) for s in nearest_stores(conn, "425 W Randolph St, Chicago, IL"): mins = round(s["travel_time_s"] / 60) print(f"{mins} min — {s['name']}") ``` ## Schema ```sql theme={null} CREATE TABLE stores ( id bigserial PRIMARY KEY, name text NOT NULL, geom geography(Point, 4326) NOT NULL, open_now boolean NOT NULL DEFAULT true ); -- The entire performance story. CREATE INDEX stores_geom_idx ON stores USING GIST (geom); ``` ## Why not query HERE for your stores? Your stores live in **your** database. Querying HERE's public place index for them is fragile and expensive, and it will silently miss the store that opened last week — map data ships on a release cadence. Your store table does not. Hours, services, and inventory change hourly. No mapping API knows them. ## Common mistakes **Ranking by straight-line distance.** Rivers. **Matrix over the entire store network** instead of a shortlist. **k routing calls to rank** instead of one matrix call. **Querying `/discover` or `/browse` for your own stores.** **Silently expanding the radius** until a result appears. A locator that cheerfully sends people 200 miles. **Routing to `position` instead of `access`.** **Computing directions on page render.** Most visitors never click. Directions on click only. **Not caching the ranking.** For a user coordinate rounded to \~3 decimal places, the drive-time ranking of nearby stores is stable for weeks. **No `GIST` index.** Sequential scan over stores. **Ignoring matrix `errorCodes`.** `3` means a route was found *but it violates a restriction*. ## Production considerations **Cache the ranked result**, keyed on rounded user coordinate. Dense urban areas produce enormous hit rates. **Geocode cache hit rate approaches 1.** Users type city names and ZIP codes, not distinct street addresses. **Directions on click, not on render.** **CDN in front of tiles.** **Filter by business rules in SQL**, before the matrix call. `open_now`, `has_pharmacy`, `in_stock` — your data, free. **Restrict browser-exposed keys by domain.** **Target cost structure:** bounded per lookup, independent of network size, dominated by cache misses. ## Related The full architecture, and the build-vs-buy decision. Modes, ceilings, and the flat array. `access` vs `position`, and confidence scoring. Why k routing calls is the wrong primitive. ## HERE documentation * [Matrix Routing v8 OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi) * [Geocode endpoint](https://docs.here.com/geocoding-and-search/docs/geocode) ## Placematic * [Pos-Eye — embeddable store locator](https://placematic.com/store-locator/) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Render a HERE Map Source: https://docs.placematic.com/examples/render-map HERE Raster Tile API v3 with Leaflet and MapLibre GL — tile URLs, truck restriction layers, CDN caching, and domain-restricting a browser-exposed key. # Render a HERE Map **Problem:** Display a HERE map in a web page. ## Prerequisites * A HERE **platform** API key with Map Rendering entitlement * `export HERE_API_KEY="..."` **Raster Tile API v3 replaces Map Tile API v2.** If you are reading a tutorial using `https://{1-4}.base.maps.ls.hereapi.com/maptile/2.1/basetile/...`, that is the **deprecated v2 API**. It also requires a HERE *developer* account. **v3 requires a HERE platform account.** Old developer-account keys do not work. ## The tile URL [https://maps.hereapi.com/v3/base/mc/\{z}/\{x}/\{y}/\{format}?style=\{style}\&apiKey=\{KEY}](https://maps.hereapi.com/v3/base/mc/\{z}/\{x}/\{y}/\{format}?style=\{style}\&apiKey=\{KEY}) | Segment | Values | | ---------- | ------------------------------------------------------------------------------------ | | `base` | `base`, `background`, `blank`, `label` | | `mc` | Mercator projection | | `{format}` | `png`, `png8`, `jpeg` | | `style` | `explore.day`, `explore.night`, `lite.day`, `satellite.day`, `explore.satellite.day` | | `size` | `256` or `512` | | `ppi` | e.g. `400` for high-DPI | Query `/v3/info` for the styles available on your account. ## The code ```javascript Leaflet theme={null} import L from "leaflet"; // Proxy this. A key in frontend JS is public. const API_KEY = window.__HERE_KEY__; const STYLE = "explore.day"; const map = L.map("map").setView([41.8845, -87.6386], 12); L.tileLayer( `https://maps.hereapi.com/v3/base/mc/{z}/{x}/{y}/png8` + `?style=${STYLE}&size=512&apiKey=${API_KEY}`, { tileSize: 512, zoomOffset: -1, // required when tileSize=512 maxZoom: 20, // Attribution is CONTRACTUAL. Not decorative. attribution: "© HERE 2026", } ).addTo(map); ``` ```javascript MapLibre GL theme={null} import maplibregl from "maplibre-gl"; const API_KEY = window.__HERE_KEY__; const map = new maplibregl.Map({ container: "map", center: [-87.6386, 41.8845], // MapLibre is [lng, lat]. HERE APIs are lat,lng. zoom: 11, style: { version: 8, sources: { "here-base": { type: "raster", tiles: [ `https://maps.hereapi.com/v3/base/mc/{z}/{x}/{y}/png8` + `?style=explore.day&size=512&apiKey=${API_KEY}`, ], tileSize: 512, attribution: "© HERE 2026", }, }, layers: [{ id: "here-base", type: "raster", source: "here-base" }], }, }); ``` ```javascript Truck restrictions layer theme={null} // The transport-attributed layer that has no Google equivalent. // Renders weight, height, and hazmat restrictions on the base map. const truckLayer = L.tileLayer( `https://maps.hereapi.com/v3/base/mc/{z}/{x}/{y}/png8` + `?style=explore.day` + `&features=vehicle_restrictions:active_and_inactive` + `&size=512&ppi=400&apiKey=${API_KEY}`, { tileSize: 512, zoomOffset: -1, attribution: "© HERE 2026" } ); // Toggleable. Overlays and base tiles bill separately — // leaving this permanently enabled is a cost decision, not a styling one. L.control.layers( { "Standard": baseLayer }, { "Truck restrictions": truckLayer } ).addTo(map); ``` ```bash cURL theme={null} # One tile, Chicago, with truck restrictions rendered curl -o tile.png \ "https://maps.hereapi.com/v3/base/mc/11/525/761/png\ ?style=explore.day\ &features=vehicle_restrictions:active_and_inactive\ &ppi=400&size=512&apiKey=${HERE_API_KEY}" # Satellite curl -o sat.png \ "https://maps.hereapi.com/v3/base/mc/15/17589/10747/png\ ?style=satellite.day&ppi=400&size=512&apiKey=${HERE_API_KEY}" # Which styles does this account have? curl "https://maps.hereapi.com/v3/info?apiKey=${HERE_API_KEY}" ``` ## Useful parameters **`features=`** selects optional tile content. Available features are **defined per style** — each style supports a different set. | Feature | Effect | | ------------------------------------------ | -------------------------------- | | `vehicle_restrictions:active_and_inactive` | Truck weight/height restrictions | | `environmental_zones:all` | Low-emission zones | | `pois:disabled` | Hide POI labels | **`pview=`** renders boundaries per a country's geopolitical view (ISO 3166-1 alpha-2). Relevant where territory is disputed. This is a legal and product decision, not a default. **`lang` / `lang2`** — BCP47, ISO 639-1 two-letter codes. (v2 used three-letter MARC codes; the parameter names and format both changed.) ## Common mistakes **Using the deprecated v2 tile host.** `base.maps.ls.hereapi.com/maptile/2.1/...` **Using a developer-account key against v3.** It requires a platform account. **Shipping an unrestricted key in frontend JavaScript.** It is public. If it has no domain restriction, it is now someone else's key and someone else's invoice. **No CDN in front of tiles.** They are static per zoom, x, y, style. This is the highest-leverage cost reduction in rendering, and it is available on any platform. **`tileSize: 512` without `zoomOffset: -1`** in Leaflet. Your map is at the wrong zoom. **Reversing coordinate order.** MapLibre and GeoJSON are `[lng, lat]`. HERE REST APIs are `lat,lng`. **Traffic overlay always on.** It is a separate meter. **Omitting attribution.** Contractual. **Assuming web tile entitlement covers the mobile SDK.** Separate entitlements. Discovered at app store submission. **Rendering a full interactive map where a static image would serve.** A thumbnail on a job card does not need panning. ## Production considerations **Put a CDN in front of tile requests.** Fix this first. **Restrict browser keys by domain.** Consider a thin server-side proxy: keeps the key private, adds a cache layer and per-tenant metering, costs latency. **Make overlays toggleable and attributable.** Traffic and restriction layers bill separately from base tiles. **Do not pre-fetch aggressively.** Tiles fetched and never viewed are pure spend. **Billing model varies by implementation.** Web tiles, mobile SDK, and static maps meter differently for what looks like the same map. Know which one you shipped before you forecast. **Query `/v3/info`** to confirm which styles your entitlement includes. ## Related Vector vs raster, entitlement boundaries, and billing models. Decoding a Flexible Polyline and drawing it. Overlaying live congestion. When you need offline and on-device rendering instead. ## HERE documentation * [HERE Raster Tile API v3](https://www.here.com/docs/category/raster-tile-api-v3) * [Map Tile v2 → Raster Tile v3 migration guide](https://docs.here.com/map-rendering/docs/migration-guide-raster-tile-api) * [Raster Tile API v3 reference](https://docs.here.com/map-rendering/reference/gettile) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Reverse Geocode Source: https://docs.placematic.com/examples/reverse-geocode Coordinates to addresses with HERE Geocoding & Search v7 — single and multi-point, coordinate rounding, and the telematics pattern that decides your bill. # Reverse Geocode **Problem:** I have a coordinate. I need the nearest address. **Do not reverse-geocode raw GPS pings on ingest.** A vehicle emitting a position every ten seconds over a nine-hour shift produces 3,240 packets. Across 200 vehicles: 648,000 per day. The address is needed when a human looks at a screen or when a stop is detected. Not at 3:47:20am on I-80. ## Prerequisites * A HERE API key with Geocoding & Search entitlement * `export HERE_API_KEY="..."` Reverse geocoding is at `revgeocode.search.hereapi.com` — a different host from forward geocoding. ## The code ```python Python theme={null} import os import time import requests HOST = "https://revgeocode.search.hereapi.com/v1/revgeocode" API_KEY = os.environ["HERE_API_KEY"] # ~5 decimal places ≈ 1 metre. GPS jitter defeats an exact-match cache; # a vehicle idling at a depot emits hundreds of points within one metre. COORD_PRECISION = 5 _cache: dict[tuple[float, float], dict] = {} class EntitlementError(Exception): ... def _key(lat: float, lng: float) -> tuple[float, float]: return (round(lat, COORD_PRECISION), round(lng, COORD_PRECISION)) def reverse_geocode( lat: float, lng: float, types: str | None = None, # e.g. "area" for city/district only radius_m: int | None = None, ) -> dict | None: """ Nearest address to a coordinate. Returns None when nothing is nearby. That is a valid outcome. """ k = _key(lat, lng) if k in _cache: return _cache[k] params = {"at": f"{lat},{lng}", "limit": 1, "apiKey": API_KEY} if types: params["types"] = types if radius_m: # Spatial filter. Without one, a coordinate in the ocean # eventually returns something. params["in"] = f"circle:{lat},{lng};r={radius_m}" backoff = 1.0 for _ in range(3): resp = requests.get(HOST, params=params, timeout=10) if resp.status_code == 403: raise EntitlementError("Reverse geocoding not entitled") if resp.status_code == 429 or resp.status_code >= 500: time.sleep(backoff); backoff *= 2; continue resp.raise_for_status() break else: raise RuntimeError("Reverse geocode failed after retries") items = resp.json().get("items", []) if not items: return None it = items[0] result = { "label": it["address"]["label"], "result_type": it["resultType"], # A NEAREST MATCH, not a truth. In rural areas this may be # several hundred metres away, on a different property. "distance_m": it.get("distance"), "position": it["position"], "here_id": it["id"], } _cache[k] = result return result def multi_reverse_geocode(points: list[dict]) -> list[dict]: """ POST /v1/multi-revgeocode — resolves a LIST in one request. If you are looping reverse_geocode() over a batch of detected stops, you have made the same mistake as looping routing to build a matrix. """ resp = requests.post( "https://revgeocode.search.hereapi.com/v1/multi-revgeocode", params={"apiKey": API_KEY}, json={"items": [ {"id": p["id"], "at": f"{p['lat']},{p['lng']}"} for p in points ]}, timeout=30, ) if resp.status_code == 403: raise EntitlementError("Multi-revgeocode not entitled") resp.raise_for_status() return resp.json().get("items", []) # --- The pattern that decides your bill ----------------------------------- def label_detected_stops(stops: list[dict]) -> list[dict]: """ Geocode EVENTS, not packets. Instrument the ratio of reverse-geocode calls to detected stops. Anything meaningfully above 1 means you are geocoding pings. """ return multi_reverse_geocode(stops) if __name__ == "__main__": # A dispatcher clicked one vehicle. Resolve ONE address. print(reverse_geocode(41.88432, -87.63877)) # Trip closed. Twelve detected stops. ONE request. stops = [{"id": f"stop_{i}", "lat": 41.88 + i * 0.01, "lng": -87.63} for i in range(12)] for item in label_detected_stops(stops): print(item.get("id"), item.get("title")) ``` ```javascript Node.js theme={null} const HOST = "https://revgeocode.search.hereapi.com/v1/revgeocode"; const MULTI = "https://revgeocode.search.hereapi.com/v1/multi-revgeocode"; const API_KEY = process.env.HERE_API_KEY; const PRECISION = 5; // ~1 metre const cache = new Map(); const key = (lat, lng) => `${lat.toFixed(PRECISION)},${lng.toFixed(PRECISION)}`; export async function reverseGeocode(lat, lng, { types, radiusM } = {}) { const k = key(lat, lng); if (cache.has(k)) return cache.get(k); const params = new URLSearchParams({ at: `${lat},${lng}`, limit: "1", apiKey: API_KEY }); if (types) params.set("types", types); if (radiusM) params.set("in", `circle:${lat},${lng};r=${radiusM}`); const ctrl = new AbortController(); const t = setTimeout(() => ctrl.abort(), 10_000); let resp; try { resp = await fetch(`${HOST}?${params}`, { signal: ctrl.signal }); } finally { clearTimeout(t); } if (resp.status === 403) throw new Error("Reverse geocoding not entitled"); if (!resp.ok) throw new Error(`HERE returned ${resp.status}`); const { items = [] } = await resp.json(); if (!items.length) return null; const result = { label: items[0].address.label, resultType: items[0].resultType, distanceM: items[0].distance, position: items[0].position, hereId: items[0].id, }; cache.set(k, result); return result; } /** One request for many coordinates. Do not loop the single endpoint. */ export async function multiReverseGeocode(points) { const resp = await fetch(`${MULTI}?apiKey=${API_KEY}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ items: points.map((p) => ({ id: p.id, at: `${p.lat},${p.lng}` })), }), }); if (!resp.ok) throw new Error(`HERE returned ${resp.status}`); return (await resp.json()).items ?? []; } ``` ```bash cURL theme={null} # Nearest address curl -G 'https://revgeocode.search.hereapi.com/v1/revgeocode' \ --data-urlencode 'at=41.88432,-87.63877' \ --data-urlencode 'limit=1' \ --data-urlencode "apiKey=${HERE_API_KEY}" # City / district only — no street address needed curl -G 'https://revgeocode.search.hereapi.com/v1/revgeocode' \ --data-urlencode 'at=53.51246,25.98294' \ --data-urlencode 'types=area' \ --data-urlencode 'limit=1' \ --data-urlencode "apiKey=${HERE_API_KEY}" # Many coordinates, one request curl -X POST "https://revgeocode.search.hereapi.com/v1/multi-revgeocode?apiKey=${HERE_API_KEY}" \ -H 'Content-Type: application/json' \ -d '{"items":[ {"id":"stop_1","at":"41.88432,-87.63877"}, {"id":"stop_2","at":"41.90280,-87.63170"} ]}' ``` ## Response walkthrough ```json theme={null} { "items": [{ "title": "32 Tremont St, Boston, MA 02108-3201, United States", "id": "here:af:streetsection:bTM9lA13maMMGHDXOCPTRA:...", "resultType": "houseNumber", "address": { "label": "...", "postalCode": "02108-3201" }, "position": { "lat": 42.35852, "lng": -71.05977 }, "access": [{ "lat": 42.3586, "lng": -71.06 }], "distance": 280, "scoring": { "queryScore": 1, "fieldScore": { "houseNumber": 1 } } }] } ``` **`distance`** — metres from your query coordinate to the returned address. **A returned address is a nearest match, not a truth.** The coordinate sits *somewhere*; HERE returns the closest addressable feature. `distance: 280` means the nearest address is 280 metres away — in rural areas, potentially on a different property. **Store the original coordinate alongside the resolved address.** **`types=area`** filters to administrative areas — city, district, region — when you do not need a street address. ## Common mistakes **Reverse-geocoding on packet ingest.** The dominant cost error in telematics. **Looping `/revgeocode` over a list.** `/multi-revgeocode` exists. **No coordinate rounding before cache lookup.** Sub-metre jitter, zero hits. **Ignoring heading.** On a divided highway, a coordinate 15 metres from the centreline resolves to either carriageway. Provide the drive direction where telematics supplies it — it is free accuracy and it removes "the truck is on the wrong side of the interstate" tickets. **Using this to snap a GPS trace to roads.** That is [Route Matching](/examples/route-matching), and only Route Matching survives an audit. **Using this for geofence containment.** That is `ST_Contains` against a polygon in your own database. See [Geofencing](/use-cases/geofencing). **Storing the nearest address as the vehicle's true location.** **Real-time endpoints for historical enrichment.** [Batch](/examples/batch-geocoding) it. **No spatial filter.** A coordinate in the ocean returns something eventually. ## Production considerations **Trigger on stop detection, not on packet arrival.** One address per stop, not 24. **Instrument the ratio.** Reverse-geocode calls ÷ detected stops. Near 1 is correct. **Cache by rounded coordinate.** \~5 decimal places. **Resolve depots and customer sites once, permanently.** Known, fixed, finite. **Batch the historical backfill.** Six months of trip history is a job, not a stream. **Geocode lazily on view.** A dispatcher watching one vehicle needs one address. Do not pre-resolve the other 199. ## Related Drive-direction snapping, spatial filters, and result types. Ingesting GPS without touching an API per packet. What this is not. Containment is a spatial query, not a geocode. ## HERE documentation * [Geocoding & Search v7 introduction](https://docs.here.com/geocoding-and-search/docs/introduction-to-here-geocoding-search-api-v7) * [Spatial references](https://docs.here.com/geocoding-and-search/docs/code-geocode-spatial-reference) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Route Matching Source: https://docs.placematic.com/examples/route-matching Map-matching a GPS trace to road segments with HERE Route Matching v8 — confidence values, warnings, and the MapVersion field that makes an audit artifact possible. # Route Matching **Problem:** I have a noisy GPS trace. I need the road segments the vehicle actually drove, defensibly. [Reverse geocoding](/examples/reverse-geocode) returns an address near a coordinate. It cannot tell you which road the vehicle drove and it will not survive an audit. These are different APIs solving different problems. Conflating them is common and it is how IFTA filings get disputed. ## Prerequisites * A HERE API key with Route Matching entitlement * `export HERE_API_KEY="..."` * A trace: GPX, NMEA, CSV, or JSON **Route Matching v8 lives at `routematching.hereapi.com`.** If you are reading a tutorial using `m.fleet.ls.hereapi.com/2/matchroute.json`, that is the legacy Fleet Telematics Route Matching API. Prefer v8 for new work. ## The code The trace is the **request body**. The endpoint is `POST /v8/match/routelinks`. ```python Python theme={null} import os import io import time import csv import requests HOST = "https://routematching.hereapi.com/v8/match/routelinks" API_KEY = os.environ["HERE_API_KEY"] # Below this, the matched link is a guess. Surface it; do not persist as fact. MIN_CONFIDENCE = 0.6 class EntitlementError(Exception): ... def trace_to_csv(points: list[dict]) -> str: """ points: [{"lat": .., "lon": .., "timestamp": epoch_ms, "speedMps": ..}, ...] Points MUST be in temporal order. A trace is a sequence, not a set. """ buf = io.StringIO() w = csv.writer(buf) w.writerow(["latitude", "longitude", "timestamp", "speedMps"]) for p in points: w.writerow([p["lat"], p["lon"], p.get("timestamp", 0), p.get("speedMps", 0)]) return buf.getvalue() def match_trace(csv_body: str, mode: str = "fastest;truck;traffic:disabled") -> dict: """ Map-match a GPS trace to road links. `routeMatch=1` requests a fully drivable, gap-free route path. `legal=...` requests warnings for traffic-rule violations along the match. """ params = { "routeMatch": 1, "mode": mode, # Warn on access restrictions, one-ways, through-traffic, illegal turns. "legal": "access,oneway,thrutraf,turn", "apiKey": API_KEY, } backoff = 2.0 for _ in range(3): resp = requests.post( HOST, params=params, data=csv_body.encode("utf-8"), headers={"Content-Type": "text/plain"}, # HERE cancels waypoint requests exceeding ~50s of computation. timeout=70, ) if resp.status_code == 403: raise EntitlementError("Route Matching not entitled") if resp.status_code == 429 or resp.status_code >= 500: time.sleep(backoff); backoff *= 2; continue resp.raise_for_status() break else: raise RuntimeError("Route matching failed after retries") return resp.json() def audit_artifact(raw_points: list[dict], response: dict) -> dict: """ THE thing you must be able to produce for a regulator. Matched output is a DERIVATION. Keep the source, and record the map version that produced it — a trip matched in March against a March map may match differently in September. Both can be correct. """ trace_points = response.get("TracePoints", []) low_conf = [ tp for tp in trace_points if tp.get("confidenceValue", 0) < MIN_CONFIDENCE ] return { "raw_trace": raw_points, # immutable source "route_links": response.get("RouteLinks", []), # the segments driven "map_version": response.get("MapVersion"), # ← cite this "matched_at": int(time.time()), "warnings": response.get("Warnings", []), "low_confidence_count": len(low_conf), } if __name__ == "__main__": points = [ {"lat": 50.4552744, "lon": 30.5213118, "timestamp": 1479954513000, "speedMps": 10.7}, {"lat": 50.4550313, "lon": 30.5205023, "timestamp": 1479954523000, "speedMps": 11.1}, # ... a full trip, segmented and downsampled BEFORE submission ] resp = match_trace(trace_to_csv(points)) artifact = audit_artifact(points, resp) print(f"Matched against map version {artifact['map_version']}") print(f"{len(artifact['route_links'])} road links") if artifact["low_confidence_count"]: print(f"⚠ {artifact['low_confidence_count']} low-confidence points — review") ``` ```javascript Node.js theme={null} const HOST = "https://routematching.hereapi.com/v8/match/routelinks"; const API_KEY = process.env.HERE_API_KEY; const MIN_CONFIDENCE = 0.6; class EntitlementError extends Error {} function traceToCsv(points) { const header = "latitude,longitude,timestamp,speedMps"; const rows = points.map( (p) => `${p.lat},${p.lon},${p.timestamp ?? 0},${p.speedMps ?? 0}` ); return [header, ...rows].join("\n"); } export async function matchTrace(csvBody, mode = "fastest;truck;traffic:disabled") { const params = new URLSearchParams({ routeMatch: "1", mode, legal: "access,oneway,thrutraf,turn", apiKey: API_KEY, }); const ctrl = new AbortController(); const t = setTimeout(() => ctrl.abort(), 70_000); // HERE cancels around 50s let resp; try { resp = await fetch(`${HOST}?${params}`, { method: "POST", headers: { "Content-Type": "text/plain" }, body: csvBody, signal: ctrl.signal, }); } finally { clearTimeout(t); } if (resp.status === 403) throw new EntitlementError("Route Matching not entitled"); if (!resp.ok) throw new Error(`HERE returned ${resp.status}`); return resp.json(); } /** Persist all four. Anything less is an assertion, not an artifact. */ export function auditArtifact(rawPoints, response) { const lowConf = (response.TracePoints ?? []) .filter((tp) => (tp.confidenceValue ?? 0) < MIN_CONFIDENCE); return { rawTrace: rawPoints, routeLinks: response.RouteLinks ?? [], mapVersion: response.MapVersion, // cite this in any dispute matchedAt: Date.now(), warnings: response.Warnings ?? [], lowConfidenceCount: lowConf.length, }; } ``` ```bash cURL theme={null} # Trace as request body. GPX, NMEA, CSV, or JSON. curl -X POST \ --data-binary "@trip-2026-07-09.csv" \ -H "Content-Type: text/plain" \ "https://routematching.hereapi.com/v8/match/routelinks?\ routeMatch=1&\ mode=fastest;truck;traffic:disabled&\ legal=access,oneway,thrutraf,turn&\ apiKey=${HERE_API_KEY}" ``` ## Response walkthrough Three keys plus `MapVersion`. Abbreviated. ```json theme={null} { "RouteLinks": [ { "linkId": -828692902, "functionalClass": 3, "shape": "50.4546089 30.5209694 50.4544411 30.5210896", "linkLength": 18.44 } ], "TracePoints": [ { "lat": 50.4552744, "lon": 30.5213118, "latMatched": 50.4546089, "lonMatched": 30.5209694, "linkIdMatched": -828692902, "confidenceValue": 0.94, "matchDistance": 4.59, "routeLinkSeqNrMatched": 10, "headingMatched": 25.2 } ], "Warnings": [ { "tracePointSeqNum": 1, "routeLinkSeqNum": 2, "category": 1005, "text": "Tracepoint #0 moved by 83 meter onto the route" } ], "MapVersion": "17116" } ``` **`RouteLinks`** — the road segments, in order, gap-free. `linkId` is the join key. **`TracePoints`** — one per input point. `linkIdMatched` joins to `RouteLinks`. `confidenceValue` is per-point. **`confidenceValue` is the field that separates a defensible match from a guess.** A dense interchange with sparse sampling produces several plausible interpretations. HERE tells you which points it was unsure about. Surface them. Do not persist a 0.4-confidence match as fact. **`Warnings`** — traffic-rule violations and matching anomalies, when `legal=` is requested. Category `1010` is "vehicle stopped"; `1005` is a point moved onto the route. These are how you detect idle periods you should have segmented out before submitting. **`MapVersion`** — the single most important field on this page for compliance work. For IFTA, the defensible artifact is: **raw trace + matched links + `MapVersion` + match timestamp.** Anything less is an assertion. A trip matched in March against map `17116` may match differently in September against a newer map. Both matches can be correct. Without recording which map produced which result, you cannot explain the discrepancy — and you will be asked to. ## Common mistakes **Using reverse geocoding to reconstruct driven routes.** Addresses, not segments. **Matching packets instead of trips.** Match on trip close. **Submitting the parked portion of a trace.** A vehicle idle for four hours emits thousands of points describing a parking space. Segment first. **Not downsampling.** Sub-second sampling adds cost and no information. **Points out of temporal order.** A trace is a sequence. **Not persisting the raw trace.** The matched output is a derivation. **Not recording `MapVersion`.** **Ignoring `confidenceValue`.** Low-confidence matches persisted as truth. **Comparing GPS speed against the *nearest* road's limit** rather than the matched link's. False violations on frontage roads parallel to highways. **Matching in real time for retrospective reporting.** Nothing is waiting. Batch it overnight. **Blaming the matcher for a ten-minute sampling interval.** Sampling rate is a device configuration decision that dominates every architectural one below it. **No timeout headroom.** HERE cancels waypoint requests exceeding roughly 50 seconds of computation. ## Production considerations **Segment trips before matching.** Detect start, stop, idle. Drop the parked portions. **Downsample.** In real telematics feeds this removes a substantial fraction of points at no information cost. **Batch, overnight.** Latency is worth nothing here and it costs money. **Store the matched result.** Re-matching for a report six months later bills again *and* may produce a different answer. **Version against the map release.** Non-negotiable for compliance. **Use truck mode for trucks.** `mode=fastest;truck;traffic:disabled`. A car-mode match against a truck's trace produces links a truck could not legally traverse. **Fix sampling rate at the device** before tuning anything upstream. ## Related Why map matching is inference, and where it goes wrong. HOS coupling, IFTA, and the audit artifact in full. Ingesting GPS without touching an API per packet. What this is not. ## HERE documentation * [Route Matching v8 get started](https://www.here.com/docs/bundle/route-matching-api-developer-guide/page/README.html) * [Match a trace](https://www.here.com/docs/bundle/route-matching-api-developer-guide/page/topics/match-trace.html) * [Route Matching v8 API reference](https://www.here.com/docs/bundle/route-matching-api-v8-api-reference/page/index.html) ## Placematic * [Route Matching](https://placematic.com/here-location-services/here-route-matching/) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. We have deployed HERE into production ELD systems. [Talk to us](https://placematic.com/contact/). # Search Nearby POIs Source: https://docs.placematic.com/examples/search-poi Finding places with HERE Geocoding & Search v7 — /discover ranks by relevance, /browse ranks by distance, and that difference is the most common integration bug. # Search Nearby POIs **Problem:** Find places near a point — by free text, or by category. Three endpoints, three different jobs. | Endpoint | Host | Ranks by | Use for | | -------------- | ----------------------------- | ------------- | --------------------------------- | | `/v1/discover` | `discover.search.hereapi.com` | **Relevance** | "coffee near Wrigley Field" | | `/v1/browse` | `browse.search.hereapi.com` | **Distance** | "pharmacies within 2 km" | | `/v1/lookup` | `lookup.search.hereapi.com` | — | Retrieve a known place by HERE ID | **Choosing wrong produces a store finder whose nearest store is third in the list, or a search box that returns the closest match instead of the best one.** This is the most common POI integration bug. ## Prerequisites * A HERE API key with Geocoding & Search entitlement * `export HERE_API_KEY="..."` **For consumer place discovery — business hours, reviews, photos, category depth — Google's data is better than HERE's. Not marginally. Categorically.** Placematic sells HERE. We would rather you know this before a migration than after. HERE is the right choice for **truck-relevant POIs** (truck stops, weigh stations, rest areas), for **your own locations** (which belong in your database, not a public index), and when you already license HERE for routing. ## The code ```python Python theme={null} import os import requests DISCOVER = "https://discover.search.hereapi.com/v1/discover" BROWSE = "https://browse.search.hereapi.com/v1/browse" LOOKUP = "https://lookup.search.hereapi.com/v1/lookup" API_KEY = os.environ["HERE_API_KEY"] class EntitlementError(Exception): ... def discover(query: str, at: str, limit: int = 10) -> list[dict]: """ Free-text place search. Results ranked by RELEVANCE. Use for: a user typed something and wants the best match. Do NOT use for: a store finder. The nearest store will not be first. """ resp = requests.get(DISCOVER, params={ "q": query, "at": at, # "lat,lng" — search context centre "limit": limit, "apiKey": API_KEY, }, timeout=10) if resp.status_code == 403: raise EntitlementError("Discover not entitled") resp.raise_for_status() return [ { "title": it["title"], "here_id": it["id"], # ← store this, use /lookup later "position": it["position"], "distance_m": it.get("distance"), "categories": [c["name"] for c in it.get("categories", [])], } for it in resp.json().get("items", []) ] def browse(at: str, categories: str | None = None, radius_m: int = 2000, limit: int = 10) -> list[dict]: """ Category-filtered places, ranked by DISTANCE from `at`. Use for: "pharmacies within 2 km", ordered nearest-first. """ params = {"at": at, "limit": limit, "apiKey": API_KEY} if categories: # HERE category IDs. See the category system reference. params["categories"] = categories if radius_m: params["in"] = f"circle:{at};r={radius_m}" resp = requests.get(BROWSE, params=params, timeout=10) if resp.status_code == 403: raise EntitlementError("Browse not entitled") resp.raise_for_status() return [ { "title": it["title"], "here_id": it["id"], "position": it["position"], "distance_m": it.get("distance"), # already sorted ascending } for it in resp.json().get("items", []) ] def lookup(here_id: str) -> dict: """ Retrieve a known place by HERE ID. Re-searching for a place you have already resolved is the POI equivalent of not caching geocoding results. """ resp = requests.get(LOOKUP, params={"id": here_id, "apiKey": API_KEY}, timeout=10) resp.raise_for_status() return resp.json() if __name__ == "__main__": # Relevance: user typed something for p in discover("coffee", at="41.9484,-87.6553", limit=5): print(f"{p['title']} ({p['distance_m']}m)") # Distance: nearest-first, filtered by category for p in browse(at="41.9484,-87.6553", radius_m=1500, limit=5): print(f"{p['distance_m']}m — {p['title']}") ``` ```bash cURL theme={null} # Relevance-ranked free-text search curl -G 'https://discover.search.hereapi.com/v1/discover' \ --data-urlencode 'q=restaurant' \ --data-urlencode 'at=52.5200,13.4050' \ --data-urlencode 'limit=5' \ --data-urlencode "apiKey=${HERE_API_KEY}" # Distance-ranked, category-filtered, within a radius curl -G 'https://browse.search.hereapi.com/v1/browse' \ --data-urlencode 'at=52.5200,13.4050' \ --data-urlencode 'in=circle:52.5200,13.4050;r=2000' \ --data-urlencode 'limit=10' \ --data-urlencode "apiKey=${HERE_API_KEY}" # Retrieve a known place — no search, no ambiguity curl -G 'https://lookup.search.hereapi.com/v1/lookup' \ --data-urlencode 'id=here:pds:place:276u33db-...' \ --data-urlencode "apiKey=${HERE_API_KEY}" ``` ## Response walkthrough ```json theme={null} { "items": [{ "title": "Café Einstein", "id": "here:pds:place:276u33db-...", "resultType": "place", "address": { "label": "Unter den Linden 42, 10117 Berlin, Germany" }, "position": { "lat": 52.51677, "lng": 13.38913 }, "access": [{ "lat": 52.51670, "lng": 13.38920 }], "distance": 214, "categories": [{ "id": "100-1000-0000", "name": "Restaurant", "primary": true }] }] } ``` **`id`** — the HERE ID. Cache it. Use `/lookup` on subsequent access. **`distance`** — metres from the `at` context centre. On `/browse` the list is already sorted by this. On `/discover` it is not. **`categories`** — HERE's taxonomy. **It does not map onto Google's.** A migration that assumes a mapping table produces silently wrong filters. ## Common mistakes **Using `/discover` for a store finder.** Relevance ranking. The nearest store appears third. **Using `/browse` for a free-text search box.** Distance ranking. The best match appears last. **Using `/geocode` for place search.** That endpoint resolves addresses. **Not caching by HERE ID.** Re-searching for a resolved place. **Assuming category taxonomies map across vendors.** **Querying a public place index for your own stores.** Your stores are in your database. A public index will silently miss the one that opened last week — map data ships on a release cadence. **POI search on map pan.** Every drag fires a query. **Expecting reviews, photos, or reliable hours.** **Using POI queries for zone containment.** That is `ST_Contains` in your own database. See [Geofencing](/use-cases/geofencing). ## Production considerations **Cache by HERE ID.** Places move slowly; users search for the same ones repeatedly. **Debounce user-facing search.** 200–300ms. **Keep your own locations in your own database.** See [Store Locator](/use-cases/store-locator). **For truck stop planning, join POIs to the route, not to a circle.** "Truck stops along the remaining route" is the question dispatchers ask; "within 10 km of the driver" is not. **Map the taxonomy explicitly** if you run both HERE and Google. **Be explicit about which surfaces stay on Google**, and why, before someone calls a deliberate hybrid a failed migration. ## Related Truck POIs, the taxonomy problem, and the honest comparison. Where the place-data gap decides your architecture. Your stores are not a POI query. The correct pattern: PostGIS shortlist, matrix rank. ## HERE documentation * [Geocoding & Search v7 introduction](https://docs.here.com/geocoding-and-search/docs/introduction-to-here-geocoding-search-api-v7) * [Developer insights and best practices](https://www.here.com/learn/blog/here-geocoding-and-search-api-developer-insights-and-best-practices) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Store Locator — End to End Source: https://docs.placematic.com/examples/store-locator A complete store locator: autocomplete, geocode, PostGIS shortlist, matrix rank, map render, directions. Four HERE APIs, three of them called once per session. # Store Locator — End to End **Problem:** A customer types an address. Show them the nearest store by drive time, on a map, with directions. This example combines four HERE APIs. **Three of them are called at most once per session, and one of them is never called at all until the user clicks "Directions."** That distribution is the design. ## The flow ```mermaid theme={null} flowchart TB TYPE[User types address] -->|debounced 250ms| AC[/v1/autocomplete/] AC --> SELECT[User selects a suggestion] SELECT -->|HERE ID| LOOKUP[/v1/lookup/] LOOKUP --> COORD[Customer coordinate
use `access`, not `position`] COORD --> PG[(PostGIS
ST_DWithin → k=10)] PG -->|zero API calls| SHORT[10 candidate stores] SHORT --> MX[/v8/matrix
1 × 10/] MX --> RANK[Ranked by drive time] RANK --> MAP[Raster Tile v3
+ markers] MAP --> CLICK{User clicks
Directions?} CLICK -->|yes| ROUTE[/v8/routes/] CLICK -->|no| DONE[No further calls] ``` | Stage | API calls | | ---------- | ------------------------------------------------ | | Typing | 1 per debounced pause, cancelled when superseded | | Selection | 1 (`/lookup`), then cached forever | | Shortlist | **0** — PostGIS | | Ranking | 1 (`/matrix`, 1 × 10) | | Map | Tiles, behind a CDN | | Directions | 1, **only on click** | Most visitors never click "Directions." Computing the route on page render bills for a feature nobody used. ## Prerequisites * HERE platform API key with Geocoding & Search, Matrix Routing, Routing, and Map Rendering entitlement * PostGIS with stores in an indexed `geography` column * `export HERE_API_KEY="..."` ## Schema ```sql theme={null} CREATE TABLE stores ( id bigserial PRIMARY KEY, name text NOT NULL, geom geography(Point, 4326) NOT NULL, opens_at time, closes_at time, has_pharmacy boolean DEFAULT false ); -- The entire performance story. Without this, sequential scan. CREATE INDEX stores_geom_idx ON stores USING GIST (geom); -- Ranking cache. Keyed on a ROUNDED customer coordinate. CREATE TABLE ranking_cache ( coord_key text PRIMARY KEY, -- "41.884,-87.639" at 3dp ranked jsonb NOT NULL, computed_at timestamptz NOT NULL DEFAULT now() ); ``` ## Backend ```python Python — the whole pipeline theme={null} import os, json, requests, psycopg2 AUTOCOMPLETE = "https://autocomplete.search.hereapi.com/v1/autocomplete" LOOKUP = "https://lookup.search.hereapi.com/v1/lookup" MATRIX = "https://matrix.router.hereapi.com/v8/matrix" ROUTER = "https://router.hereapi.com/v8/routes" API_KEY = os.environ["HERE_API_KEY"] K = 10 # shortlist size — bounds the matrix, and the bill COORD_DP = 3 # ~110 m. Ranking is stable within a block. class EntitlementError(Exception): ... # ── 1. Autocomplete ─────────────────────────────────────────────────────── def suggest(query: str, at: str, country: str = "USA") -> list[dict]: """ Debouncing lives in the CLIENT. This endpoint bills per request. Calling it per keystroke from a backend moves the problem, not solves it. """ if len(query) < 3: return [] r = requests.get(AUTOCOMPLETE, params={ "q": query, "at": at, "in": f"countryCode:{country}", "limit": 5, "apiKey": API_KEY, }, timeout=5) if r.status_code == 403: raise EntitlementError("Autocomplete not entitled") r.raise_for_status() return [ {"label": it["address"]["label"], "here_id": it["id"], # ← the key. Never re-geocode the label. "highlights": it.get("highlights")} for it in r.json().get("items", []) ] # ── 2. Resolve the selection ────────────────────────────────────────────── def resolve(here_id: str) -> dict: """ The user handed you a HERE ID. Use it. Re-geocoding the label string is a wasted call and a worse match. """ r = requests.get(LOOKUP, params={"id": here_id, "apiKey": API_KEY}, timeout=5) r.raise_for_status() it = r.json() # `access` is the road-network entry point. Route to this, not `position`. return (it.get("access") or [it["position"]])[0] # ── 3. Shortlist. Zero API calls. ───────────────────────────────────────── def shortlist(conn, lat: float, lng: float, open_now: bool = True) -> list[dict]: """ <-> is the KNN operator. Sub-millisecond against the GIST index. Business filters (open_now, has_pharmacy) run here, for free, BEFORE anything is sent to HERE. """ with conn.cursor() as cur: cur.execute(""" SELECT id, name, ST_Y(geom::geometry), ST_X(geom::geometry) FROM stores WHERE (%s = false OR (now()::time BETWEEN opens_at AND closes_at)) ORDER BY geom <-> ST_SetSRID(ST_MakePoint(%s,%s),4326)::geography LIMIT %s """, (open_now, lng, lat, K)) # MakePoint takes (lng, lat) return [{"id": r[0], "name": r[1], "lat": r[2], "lng": r[3]} for r in cur.fetchall()] # ── 4. Rank by drive time. ONE matrix call. ─────────────────────────────── def rank(origin: dict, stores: list[dict]) -> list[dict]: """ 1 origin × 10 destinations = ONE request. Ten routing calls would be ten. That ratio is the cost structure. """ body = { "origins": [{"lat": origin["lat"], "lng": origin["lng"]}], "destinations": [{"lat": s["lat"], "lng": s["lng"]} for s in stores], "regionDefinition": {"type": "circle", "center": {"lat": origin["lat"], "lng": origin["lng"]}, "radius": 100_000}, "matrixAttributes": ["travelTimes", "distances"], } r = requests.post(MATRIX, params={"apiKey": API_KEY, "async": "false"}, json=body, timeout=20) if r.status_code == 403: raise EntitlementError("Matrix Routing not entitled") r.raise_for_status() m = r.json()["matrix"] n = m["numDestinations"] errs = m.get("errorCodes") or [0] * n out = [] for j, s in enumerate(stores): idx = 0 * n + j # flat, row-major. origin index is 0. if errs[idx]: # 1=disconnected 2=nomatch 3=restriction continue out.append({**s, "travel_time_s": m["travelTimes"][idx], "distance_m": m["distances"][idx]}) return sorted(out, key=lambda s: s["travel_time_s"]) # ── 5. Directions. ON CLICK ONLY. ───────────────────────────────────────── def directions(origin: dict, store: dict) -> dict: r = requests.get(ROUTER, params={ "transportMode": "car", "origin": f"{origin['lat']},{origin['lng']}", "destination": f"{store['lat']},{store['lng']}", "return": "summary,polyline", # explicit. never the default. "apiKey": API_KEY, }, timeout=15) r.raise_for_status() body = r.json() if not body.get("routes"): # HTTP 200 ≠ route found raise ValueError(f"No route: {body.get('notice')}") s = body["routes"][0]["sections"][0] return {"duration_s": s["summary"]["duration"], "length_m": s["summary"]["length"], "polyline": s["polyline"]} # ── Orchestration with the ranking cache ────────────────────────────────── def nearest_stores(conn, here_id: str) -> list[dict]: coord = resolve(here_id) key = f"{coord['lat']:.{COORD_DP}f},{coord['lng']:.{COORD_DP}f}" with conn.cursor() as cur: cur.execute("""SELECT ranked FROM ranking_cache WHERE coord_key = %s AND computed_at > now() - interval '7 days'""", (key,)) hit = cur.fetchone() if hit: return hit[0] # zero HERE calls ranked = rank(coord, shortlist(conn, coord["lat"], coord["lng"])) with conn.cursor() as cur: cur.execute("""INSERT INTO ranking_cache (coord_key, ranked) VALUES (%s,%s) ON CONFLICT (coord_key) DO UPDATE SET ranked = EXCLUDED.ranked, computed_at = now()""", (key, json.dumps(ranked))) conn.commit() return ranked ``` ```javascript Frontend — debounce + cancellation theme={null} const AC = "https://autocomplete.search.hereapi.com/v1/autocomplete"; const DEBOUNCE_MS = 250; const MIN_CHARS = 3; let timer = null; let inFlight = null; /** * Two mechanisms prevent the bill: * 1. debounce — one request per typing pause * 2. AbortController — cancel superseded requests * Without both, a 20-character address is 20 transactions. */ function onInput(query, at) { clearTimeout(timer); if (query.length < MIN_CHARS) return render([]); timer = setTimeout(async () => { inFlight?.abort(); // the user typed more. stale. const ctrl = new AbortController(); inFlight = ctrl; const params = new URLSearchParams({ q: query, at, limit: "5" }); try { // Proxy through YOUR backend. A key in frontend JS is public. const r = await fetch(`/api/suggest?${params}`, { signal: ctrl.signal }); render(await r.json()); } catch (e) { if (e.name !== "AbortError") throw e; // AbortError is normal } }, DEBOUNCE_MS); } ``` ## Rendering ```javascript Leaflet — tiles, markers, route theme={null} import L from "leaflet"; import { decode } from "@here/flexpolyline"; const map = L.map("map").setView([41.8845, -87.6386], 12); // Raster Tile v3. NOT the deprecated base.maps.ls.hereapi.com v2 host. L.tileLayer( `/tiles/{z}/{x}/{y}/png8?style=explore.day&size=512`, // your CDN proxy { tileSize: 512, zoomOffset: -1, attribution: "© HERE 2026" } ).addTo(map); function showStores(ranked) { ranked.forEach((s, i) => { L.marker([s.lat, s.lng]) .bindPopup(`${s.name} — ${Math.round(s.travel_time_s / 60)} min`) .addTo(map); }); } async function showDirections(storeId) { const { polyline } = await fetch(`/api/directions/${storeId}`).then(r => r.json()); // Flexible Polyline. NOT Google's encoded polyline algorithm. const coords = decode(polyline).polyline; // [[lat, lng], ...] L.polyline(coords, { weight: 5 }).addTo(map); } ``` ## Where the cost is | Surface | Bounded by | | ------------ | ------------------------------------------------- | | Autocomplete | Typing pauses × sessions. Debounce is the lever | | Lookup | Distinct addresses ever entered. Approaches zero | | Matrix | Cache misses × 1. Bounded by `K`, not store count | | Tiles | CDN hit rate | | Directions | Clicks, not page views | **A 5,000-store network and a 50-store network cost the same per lookup.** The shortlist bounds the matrix; the matrix does not scale with your network. That is why stage 3 exists. ## Common mistakes **Ranking by straight-line distance.** A store 800 m away across a river is not close. This is the defect that gets locators rebuilt. **Matrix over the whole network.** Shortlist first. **Ten routing calls instead of one matrix call.** **Querying `/discover` or `/browse` for your own stores.** Your stores are in your database. A public place index will silently miss the one that opened last week. **Undebounced autocomplete.** **No `AbortController`.** Paying to receive stale responses. **Re-geocoding the selected label** instead of `/lookup` by `id`. **Routing to `position` instead of `access`.** **Directions on page render.** **No `GIST` index.** **Silently expanding the radius** until something is found. A locator that cheerfully sends people 200 miles. **Unrestricted API key in frontend JavaScript.** **No CDN on tiles.** ## Related The architecture, and the build-versus-buy decision. Modes, ceilings, and the flat row-major array. Raster Tile v3, CDN, domain restriction. Debouncing, cancellation, and `/autocomplete` vs `/autosuggest`. ## HERE documentation * [Geocoding & Search v7](https://docs.here.com/geocoding-and-search/docs/introduction-to-here-geocoding-search-api-v7) * [Matrix Routing v8 OpenAPI](https://matrix.router.hereapi.com/v8/openapi) * [Routing API v8](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/get-started.html) * [Raster Tile API v3](https://www.here.com/docs/category/raster-tile-api-v3) * [Flexible Polyline decoders](https://github.com/heremaps/flexible-polyline) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Tour Planning Source: https://docs.placematic.com/examples/tour-planning Solving a Vehicle Routing Problem with HERE Tour Planning v3 — the async lifecycle, the profile reference that trips everyone, and unassigned jobs as a first-class outcome. # Tour Planning **Problem:** Twelve vehicles, sixty stops, appointment windows, capacity. Which vehicle goes where, in what order? If you are writing a nearest-neighbour heuristic to avoid a licensing conversation, stop. **HERE documents Tour Planning as included in the HERE Base Plan.** Confirm your own entitlement — Base Plan inclusion and your contract's inclusion are separate facts. ## Prerequisites * A HERE API key or OAuth token with Tour Planning entitlement * `export HERE_API_KEY="..."` ## The code ```python Python theme={null} import os import time import requests BASE = "https://tourplanning.hereapi.com/v3" API_KEY = os.environ["HERE_API_KEY"] class EntitlementError(Exception): ... class SolveTimeout(Exception): """status=timeout. NOT an error. Raise maxTime or shrink the problem.""" def build_problem(jobs: list[dict], depot: dict, vehicle_count: int) -> dict: """ A problem has two halves: `plan` (the jobs) and `fleet` (the vehicles). CRITICAL: fleet.types[].profile is a STRING REFERENCE into fleet.profiles[].name. It is NOT a transport mode. Writing "profile": "truck" with no profile NAMED truck fails confusingly. """ return { "plan": { "jobs": [ { "id": j["id"], "tasks": { "deliveries": [{ "places": [{ "location": {"lat": j["lat"], "lng": j["lng"]}, # Appointment window. A constraint, not a preference. "times": [[j["window_start"], j["window_end"]]], "duration": j["service_time_s"], }], "demand": [j["demand"]], }] }, } for j in jobs ] }, "fleet": { "types": [{ "id": "van", "profile": "van_profile", # ← reference, see below "costs": { # This is where you encode BUSINESS POLICY. # High `fixed` discourages using an extra vehicle. # Leaving these at doc defaults is why the solver # uses every vehicle you gave it. "distance": 0.0002, "time": 0.005, "fixed": 22, }, "shifts": [{ "start": {"time": depot["shift_start"], "location": {"lat": depot["lat"], "lng": depot["lng"]}}, "end": {"time": depot["shift_end"], "location": {"lat": depot["lat"], "lng": depot["lng"]}}, }], "limits": {"maxDistance": 300000, "shiftTime": 28800}, "capacity": [100], "amount": vehicle_count, }], "profiles": [{ "name": "van_profile", # ← the target of the reference "type": "car", # the actual transport mode "departureTime": depot["shift_start"], }], }, "configuration": { "termination": { # HERE's example sets maxTime: 2. On a 60-stop problem that # produces a legal solution a dispatcher will reject. # Find your knee empirically. "maxTime": 60, "stagnationTime": 20, } }, } def submit_async(problem: dict) -> tuple[str, str]: """POST /v3/problems/async → (statusId, href). Persist BOTH before polling.""" resp = requests.post( f"{BASE}/problems/async", params={"apikey": API_KEY}, json=problem, timeout=30, ) if resp.status_code == 403: raise EntitlementError("Tour Planning not entitled") resp.raise_for_status() data = resp.json() # Use the href HERE returns. Do not construct it. return data["statusId"], data["href"] def poll(href: str, max_wait_s: int = 900) -> str: """Poll while pending/inProgress. Returns the solution href on success.""" backoff, waited = 3.0, 0.0 while waited < max_wait_s: resp = requests.get(href, params={"apikey": API_KEY}, timeout=15) resp.raise_for_status() body = resp.json() status = body["status"] if status == "success": return body["resource"]["href"] if status == "timeout": raise SolveTimeout("Solver did not converge — raise maxTime") if status == "failure": raise RuntimeError(f"Solve failed: {body}") time.sleep(backoff) waited += backoff backoff = min(backoff * 1.4, 30.0) raise TimeoutError("Polling exceeded budget") def fetch_solution(solution_href: str) -> dict: resp = requests.get(solution_href, params={"apikey": API_KEY}, timeout=60) resp.raise_for_status() return resp.json() if __name__ == "__main__": problem = build_problem(jobs=[...], depot={...}, vehicle_count=12) status_id, href = submit_async(problem) # PERSIST status_id AND href NOW. A worker restart must resume, not resubmit. solution = fetch_solution(poll(href)) # Unassigned jobs are a FIRST-CLASS OUTCOME, not an error. # Capacity, windows, or shift time made them unservable. # Surface them to a dispatcher BEFORE the day starts. unassigned = solution.get("unassigned", []) if unassigned: for u in unassigned: print(f"UNASSIGNED {u['jobId']}: {u.get('reasons')}") for tour in solution.get("tours", []): stops = [s["activities"][0]["jobId"] for s in tour["stops"] if s.get("activities")] print(f"{tour['vehicleId']}: {len(stops)} stops") ``` ```bash cURL theme={null} # Synchronous — for small problems only. curl -X POST 'https://tourplanning.hereapi.com/v3/problems' \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer ${HERE_TOKEN}" \ -d @problem.json # Asynchronous — what you want at scale. curl -X POST 'https://tourplanning.hereapi.com/v3/problems/async' \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer ${HERE_TOKEN}" \ -d @problem.json # → { "statusId": "...", "href": "https://tourplanning.hereapi.com/v3/status/..." } # Poll the returned href. Do NOT construct it. curl "https://tourplanning.hereapi.com/v3/status/${STATUS_ID}?apikey=${HERE_API_KEY}" # → { "status": "success", "resource": { "resourceId": "...", "href": ".../solution" } } # Retrieve curl "https://tourplanning.hereapi.com/v3/problems/${PROBLEM_ID}/solution?apikey=${HERE_API_KEY}" ``` **Both auth methods work.** Drop the `Authorization: Bearer` header and append `?apikey=` to the URL. This applies to all Tour Planning endpoints. ## The `profile` reference This trips up nearly every first integration. ```json theme={null} { "fleet": { "types": [{ "id": "van", "profile": "van_profile", ... }], "profiles": [{ "name": "van_profile", "type": "car", ... }] } } ``` `fleet.types[].profile` is a **string key** into `fleet.profiles[].name`. It is not a transport mode. `"profile": "truck"` with no profile *named* `truck` produces a confusing failure. The transport mode lives in `profiles[].type`. ## The async lifecycle ```text theme={null} POST /v3/problems/async -> response contains statusId and href; persist both immediately GET the returned href -> status: pending or inProgress -> status: success, with a resource href -> status: timeout -> status: failure GET the returned resource href -> solution payload ``` ## Unassigned jobs ```json theme={null} { "tours": [...], "unassigned": [ { "jobId": "job_47", "reasons": [{ "code": "CAPACITY_CONSTRAINT", "description": "..." }] } ] } ``` The solver will drop jobs that capacity, time windows, or shift time make unservable. **Your integration must handle a solution that does not serve every stop.** A dispatcher UI that silently omits them is worse than useless. Surface them, with reasons, before the shift begins — not at 4pm when a customer calls. ## Common mistakes **Approximating this with N routing calls.** Slower, more expensive, worse routes. **Setting `profile` to a transport mode.** It is a reference into `fleet.profiles`. **Leaving `maxTime: 2` from the documentation example.** Legal solution, unusable sequence. **Treating `timeout` as a failure.** It means "raise `maxTime`." **Ignoring `unassigned`.** The solver told you. **Blocking an HTTP request on a synchronous solve.** Use `/async`. **Constructing the status or solution URL.** Use the returned `href`. **Losing `statusId` on restart.** Resubmission bills again and returns a *different* sequence, confusing everyone downstream. **Leaving `costs` at example values.** `fixed` is how you tell the solver whether adding a vehicle is cheap or expensive. That is a business decision. **Optimizing a truck fleet on car profiles.** Infeasible sequences, confidently returned. **Re-solving on every inbound order.** Route churn. Drivers stop trusting the app. ## Production considerations **Solve on a cadence, not on every event.** Nightly. Locked schedule. Exception-driven replanning only. A stable, 90%-optimal schedule outperforms a perfect one nobody follows. **Persist `statusId` before the first poll.** The solve runs on HERE's side regardless. **Hash the problem.** Unchanged jobs, fleet, and constraints → reuse the solution. **Mid-day replanning is a new problem.** Current vehicle location becomes the shift start; completed jobs are removed. Fresh submission, not an incremental update. **Service time estimates are your accuracy ceiling.** Instrument actual versus estimated per job type. This improves schedules more than any solver parameter. **Tune `maxTime` and `stagnationTime` against real problem size.** Measure the marginal improvement. Find the knee. **Truck fleets need truck profiles**, with the same vehicle constraints as [Truck Route](/examples/truck-route). ## Related VRP variants, costs, priorities, reloads. Skills, appointment windows, and why re-solving destroys trust. Where service time dominates travel time. The cost table underneath — and the interface to your own solver. ## HERE documentation * [Tour Planning introduction](https://docs.here.com/tour-planning/docs/introduction-tour-planning) * [Quick start](https://docs.here.com/tour-planning/docs/quick-start) * [Developer guide](https://docs.here.com/tour-planning/docs/introduction-tour-planning) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Traffic Flow Source: https://docs.placematic.com/examples/traffic-flow Real-time traffic flow and incidents with HERE Traffic API v7 — geospatial filters, jam factor, and why polling a bounding box is not a live map. # Traffic Flow **Problem:** I need current speed and congestion on the roads in an area. ## Prerequisites * A HERE API key with Traffic entitlement * `export HERE_API_KEY="..."` **Traffic v7 is a data API, not a tile API.** It returns JSON describing road segments, not images. Two parameters are mandatory on every request: **`in`** (geospatial filter) and **`locationReferencing`**. Omitting `locationReferencing` returns `E608051`. ## The code ```python Python theme={null} import os import time import requests BASE = "https://data.traffic.hereapi.com/v7" API_KEY = os.environ["HERE_API_KEY"] class EntitlementError(Exception): ... def traffic_flow( center_lat: float, center_lng: float, radius_m: int = 2000, min_jam_factor: float | None = None, location_referencing: str = "shape", # shape | tmc | olr | none ) -> list[dict]: """ Real-time flow for a circular area. `locationReferencing` is MANDATORY. Request ONLY the scheme you can actually interpret — asking for `shape` when you only use `olr` returns geometry you will parse and discard. Use `none` if you only want jam factors and do not need geometry at all. """ params = { "in": f"circle:{center_lat},{center_lng};r={radius_m}", "locationReferencing": location_referencing, "apiKey": API_KEY, } if min_jam_factor is not None: # Server-side filter. Do NOT fetch everything and filter locally. params["minJamFactor"] = min_jam_factor backoff = 1.0 for _ in range(3): resp = requests.get(f"{BASE}/flow", params=params, timeout=15) if resp.status_code == 403: raise EntitlementError("Traffic not entitled") if resp.status_code == 429 or resp.status_code >= 500: time.sleep(backoff); backoff *= 2; continue resp.raise_for_status() break else: raise RuntimeError("Traffic flow failed after retries") out = [] for item in resp.json().get("results", []): flow = item["currentFlow"] out.append({ "street": item["location"].get("description"), "length_m": item["location"]["length"], "speed_mps": flow.get("speed"), "free_flow_mps": flow.get("freeFlow"), # 0.0 = free flow. 10.0 = road closed / standstill. "jam_factor": flow.get("jamFactor"), "traversability": flow.get("traversability"), }) return out def traffic_incidents(bbox: str, lang: str = "en-US") -> list[dict]: """ Incidents for a bounding box. `bbox:west,south,east,north` Flow signals closures via jamFactor. For closures specifically, HERE recommends Incidents, not Flow. """ resp = requests.get( f"{BASE}/incidents", params={ "in": f"bbox:{bbox}", "locationReferencing": "shape", "lang": lang, # descriptions default to the country's language "apiKey": API_KEY, }, timeout=15, ) if resp.status_code == 403: raise EntitlementError("Traffic not entitled") resp.raise_for_status() return [ { "id": r["incidentDetails"]["id"], "type": r["incidentDetails"]["type"], "criticality": r["incidentDetails"]["criticality"], "road_closed": r["incidentDetails"]["roadClosed"], "start": r["incidentDetails"]["startTime"], "end": r["incidentDetails"]["endTime"], "description": r["incidentDetails"].get("description", {}).get("value"), } for r in resp.json().get("results", []) ] if __name__ == "__main__": # Only segments with meaningful congestion. Filter server-side. jams = traffic_flow(51.50643, -0.12719, radius_m=1000, min_jam_factor=4.0) for j in sorted(jams, key=lambda x: -(x["jam_factor"] or 0))[:5]: print(f"{j['street']}: jamFactor {j['jam_factor']}") ``` ```javascript Node.js theme={null} const BASE = "https://data.traffic.hereapi.com/v7"; const API_KEY = process.env.HERE_API_KEY; class EntitlementError extends Error {} export async function trafficFlow(lat, lng, { radiusM = 2000, minJamFactor, locationReferencing = "shape", // mandatory. use "none" if you skip geometry } = {}) { const params = new URLSearchParams({ in: `circle:${lat},${lng};r=${radiusM}`, locationReferencing, apiKey: API_KEY, }); if (minJamFactor != null) params.set("minJamFactor", String(minJamFactor)); const ctrl = new AbortController(); const t = setTimeout(() => ctrl.abort(), 15_000); let resp; try { resp = await fetch(`${BASE}/flow?${params}`, { signal: ctrl.signal }); } finally { clearTimeout(t); } if (resp.status === 403) throw new EntitlementError("Traffic not entitled"); if (!resp.ok) throw new Error(`HERE returned ${resp.status}`); const { results = [] } = await resp.json(); return results.map((item) => ({ street: item.location.description, lengthM: item.location.length, speedMps: item.currentFlow.speed, freeFlowMps: item.currentFlow.freeFlow, jamFactor: item.currentFlow.jamFactor, // 0 free-flow … 10 standstill traversability: item.currentFlow.traversability, })); } ``` ```bash cURL theme={null} # Flow in a 1 km circle. API key auth. curl -G 'https://data.traffic.hereapi.com/v7/flow' \ --data-urlencode 'in=circle:51.50643,-0.12719;r=1000' \ --data-urlencode 'locationReferencing=shape' \ --data-urlencode "apiKey=${HERE_API_KEY}" # Incidents in a bounding box. OAuth Bearer also supported. curl -H "Authorization: Bearer ${HERE_TOKEN}" \ 'https://data.traffic.hereapi.com/v7/incidents?in=bbox:13.400,52.500,13.405,52.505&locationReferencing=olr' # Only significant congestion. Filter server-side, not in your app. curl -G 'https://data.traffic.hereapi.com/v7/flow' \ --data-urlencode 'in=circle:51.50643,-0.12719;r=1000' \ --data-urlencode 'locationReferencing=none' \ --data-urlencode 'minJamFactor=7' \ --data-urlencode "apiKey=${HERE_API_KEY}" ``` ## Geospatial filters `in` accepts four formats. Choose the smallest that covers your question. | Format | Example | | ------------ | ---------------------------------- | | Circle | `circle:52.50811,13.47853;r=2000` | | Bounding box | `bbox:13.088,52.338,13.761,52.675` | | Corridor | `corridor:{flexiblePolyline};r=50` | | Tile | `tile:377894440` | **`corridor:` is the one most teams miss.** It accepts a Flexible Polyline plus a radius. For "what is the traffic on this route right now," a corridor around the route polyline is dramatically cheaper and more relevant than a bounding box containing the whole city. ## Response walkthrough **Flow:** ```json theme={null} { "results": [{ "location": { "description": "Invalidenstraße", "length": 279.0, "shape": {...} }, "currentFlow": { "speed": 8.33, "freeFlow": 13.89, "jamFactor": 6.4, "confidence": 0.95, "traversability": "open" } }]} ``` **`jamFactor`** runs 0.0 (free flow) to 10.0 (standstill or closed). It is the field you filter and alert on. **`speed` and `freeFlow` are metres per second.** Not km/h. Flow indicates closed roads via `jamFactor`. **For closures specifically, HERE recommends the Incidents endpoint**, not Flow. If road closures drive a routing decision in your system, query `/v7/incidents`. **Incidents:** ```json theme={null} { "results": [{ "location": { "length": 9310.0, "olr": "CCkBEAAlJAUSUiRTrwAJ..." }, "incidentDetails": { "id": "3195531507295233286", "startTime": "2019-12-03T23:00:00Z", "endTime": "2023-03-12T22:59:59Z", "roadClosed": true, "criticality": "minor", "type": "other", "description": { "value": "...", "language": "de" } } }]} ``` Descriptions default to the incident country's language. Pass `lang=en-US` for English. ## `locationReferencing` | Value | Returns | Use when | | ------- | ----------------------------- | --------------------------------- | | `shape` | WGS84 coordinates | You render on a map | | `tmc` | Traffic Message Channel codes | Legacy broadcast integration | | `olr` | OpenLR references | Cross-map-vendor segment matching | | `none` | No location at all | You only need jam factors | **Request only what you can interpret.** Asking for `shape` when you use `olr` returns geometry you parse and discard. ## Common mistakes **Omitting `locationReferencing`.** `E608051`. It is mandatory. **Filtering jam factor client-side.** Use `minJamFactor` / `maxJamFactor`. **Treating `speed` as km/h.** Metres per second. **Polling a city-wide bounding box for a live map.** Use `corridor:` around your route, or `tile:` for a viewport. **Using Flow to detect closures.** Use Incidents. **Requesting `shape` when you do not render geometry.** Use `none`. **Assuming Traffic replaces `departureTime` on a routing call.** The Routing API applies traffic internally. This API is for *displaying* or *analysing* traffic, not for making routes traffic-aware. ## Production considerations **Do not poll on map pan.** Every drag becomes a request. Debounce, and cache per tile. **Corridor filters for route-relevant traffic.** Bounding boxes return everything. **Cache with a short TTL.** Traffic is near-real-time; it is not per-second. **This is a separate meter.** Traffic bills independently of routing and tiles. Traffic overlays left permanently enabled are a cost decision, not a styling one. **`functionalClasses` narrows to major roads.** A dispatcher watching highways does not need residential streets. **`E608xxx` codes are malformed-query errors.** Read the message; do not retry. ## Related Traffic as a rendering layer, and why it bills separately. `departureTime` on a routing call, not a Traffic query. Where traffic enters dispatch decisions. Pattern 1: the unit of work. Do not poll per pan. ## HERE documentation * [Traffic API v7 introduction](https://docs.here.com/traffic-api/docs/introduction-to-here-traffic-api-v7) * [Get started](https://docs.here.com/traffic-api/docs/send-request-readme) * [Flow concepts](https://docs.here.com/traffic-api/docs/flow) * [Traffic v7 API reference](https://www.here.com/docs/bundle/traffic-api-v7-api-reference/page/index.html) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Calculate a Truck Route Source: https://docs.placematic.com/examples/truck-route Commercial vehicle routing with HERE Routing v8 — physical constraints, the units that will catch you, and CI assertions against real trap geometry. # Calculate a Truck Route **Problem:** I need a route a 53-foot trailer can physically and legally drive. `transportMode=truck` selects the truck routing **engine**. It does not tell HERE how tall your vehicle is. Omit the dimensions and you get a route. It returns `200`. It contains a polyline. It may route a 4.1-metre trailer under a 3.4-metre bridge, and HERE will not warn you, because you did not tell it there was a trailer. ## Prerequisites * A HERE API key with Routing entitlement * **Confirm truck routing entitlement specifically** — free-tier product coverage varies and should not be assumed * `export HERE_API_KEY="..."` ## The code ```python Python theme={null} import os import time from dataclasses import dataclass, asdict import requests HOST = "https://router.hereapi.com/v8/routes" API_KEY = os.environ["HERE_API_KEY"] @dataclass(frozen=True) class TruckProfile: """ Vehicle constraints. These belong on the VEHICLE RECORD in your database, never hardcoded at a call site. A refactor that drops one is invisible in code review and returns 200. UNITS ARE NOT OBVIOUS AND ARE NOT VALIDATED FOR PLAUSIBILITY. """ height_cm: int # centimetres. 410 = 4.1 m gross_weight_kg: int # kilograms axle_count: int # base vehicle + ALL trailers width_cm: int | None = None length_cm: int | None = None trailer_count: int | None = None def to_params(self) -> dict: p = { "vehicle[height]": self.height_cm, "vehicle[grossWeight]": self.gross_weight_kg, "vehicle[axleCount]": self.axle_count, } if self.width_cm: p["vehicle[width]"] = self.width_cm if self.length_cm: p["vehicle[length]"] = self.length_cm if self.trailer_count: p["vehicle[trailerCount]"] = self.trailer_count return p class EntitlementError(Exception): ... class NoRouteError(Exception): """No LEGAL path exists. Correct outcome. Do not relax constraints.""" def truck_route(origin: str, destination: str, profile: TruckProfile) -> dict: if profile.height_cm > 500: raise ValueError("height_cm looks like millimetres") if profile.height_cm < 100: raise ValueError("height_cm looks like metres — you meant centimetres") params = { "transportMode": "truck", "origin": origin, "destination": destination, "return": "summary,polyline", "apiKey": API_KEY, **profile.to_params(), } backoff = 1.0 for _ in range(3): resp = requests.get(HOST, params=params, timeout=20) if resp.status_code == 403: raise EntitlementError("Truck routing not entitled") if resp.status_code == 429 or resp.status_code >= 500: time.sleep(backoff); backoff *= 2; continue resp.raise_for_status() break else: raise RuntimeError("Truck routing failed after retries") body = resp.json() if not body.get("routes"): raise NoRouteError(str(body.get("notice", []))) s = body["routes"][0]["sections"][0] return {"duration_s": s["summary"]["duration"], "length_m": s["summary"]["length"], "polyline": s.get("polyline")} if __name__ == "__main__": fleet_profile = TruckProfile(height_cm=410, gross_weight_kg=35000, axle_count=5) try: r = truck_route("42.0641,-88.0509", "41.5250,-88.0817", fleet_profile) print(r["duration_s"], r["length_m"]) except NoRouteError: # Escalate to a dispatcher. Do NOT fall back to car routing. print("No legal route for this vehicle") ``` ```javascript Node.js theme={null} const HOST = "https://router.hereapi.com/v8/routes"; const API_KEY = process.env.HERE_API_KEY; class EntitlementError extends Error {} class NoRouteError extends Error {} const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); /** * @param {{heightCm:number, grossWeightKg:number, axleCount:number}} profile * Constraints belong on the vehicle record. Never hardcode them here. * heightCm is CENTIMETRES. Passing 4.1 declares a 4cm truck. */ export async function truckRoute(origin, destination, profile) { if (profile.heightCm < 100 || profile.heightCm > 500) { throw new Error(`heightCm=${profile.heightCm} — wrong unit?`); } const params = new URLSearchParams({ transportMode: "truck", origin, destination, return: "summary,polyline", "vehicle[height]": String(profile.heightCm), "vehicle[grossWeight]": String(profile.grossWeightKg), "vehicle[axleCount]": String(profile.axleCount), apiKey: API_KEY, }); let backoff = 1000; let resp; for (let i = 0; i < 3; i++) { const ctrl = new AbortController(); const t = setTimeout(() => ctrl.abort(), 20_000); try { resp = await fetch(`${HOST}?${params}`, { signal: ctrl.signal }); } finally { clearTimeout(t); } if (resp.status === 403) throw new EntitlementError("Truck routing not entitled"); if (resp.status === 429 || resp.status >= 500) { await sleep(backoff + Math.random() * 250); backoff *= 2; continue; } if (!resp.ok) throw new Error(`HERE returned ${resp.status}`); break; } const body = await resp.json(); if (!body.routes?.length) throw new NoRouteError(JSON.stringify(body.notice ?? [])); const { summary, polyline } = body.routes[0].sections[0]; return { durationS: summary.duration, lengthM: summary.length, polyline }; } ``` ```bash cURL theme={null} curl -gX GET 'https://router.hereapi.com/v8/routes' \ --data-urlencode 'transportMode=truck' \ --data-urlencode 'origin=42.0641,-88.0509' \ --data-urlencode 'destination=41.5250,-88.0817' \ --data-urlencode 'vehicle[height]=410' \ --data-urlencode 'vehicle[grossWeight]=35000' \ --data-urlencode 'vehicle[axleCount]=5' \ --data-urlencode 'return=summary,polyline' \ --data-urlencode "apiKey=${HERE_API_KEY}" \ -G ``` **`vehicle` supersedes `truck`.** The `truck` object is marked deprecated in the current v8 specification. `weightLimit` is deprecated in favour of `grossWeight`. If you are reading a 2021 tutorial, you are reading deprecated parameters. ## Units | Parameter | Unit | A common wrong value | | ---------------------- | --------------- | ------------------------------- | | `vehicle[height]` | **centimetres** | `4.1` → a four-centimetre truck | | `vehicle[grossWeight]` | **kilograms** | `35` → a 35 kg truck | | `vehicle[axleCount]` | count, 2–255 | tractor only, omitting trailers | Units are not validated for plausibility. Pass `4.1` and HERE routes a four-centimetre vehicle under every bridge in North America. The response is `200`. The validation in the Python example above exists for exactly this reason. ## Verify the constraints are actually applied **Do not ship truck routing without this test.** It is the reason bridge strikes make the news. Route a 410 cm vehicle through these. **Any path returned is a failure.** | Location | Coordinates (approx) | | -------------------------------------- | --------------------------------------- | | 11foot8 bridge, Durham NC | `35.9993,-78.9105` → `35.9975,-78.9060` | | Storrow Drive, Boston MA | Commercial vehicles prohibited | | Southern State Parkway, Long Island NY | Commercial vehicles prohibited | ```python theme={null} import pytest TRAPS = [ ("11foot8", "35.9993,-78.9105", "35.9975,-78.9060"), # Add Storrow Drive and Southern State Parkway coordinates # verified against your own map inspection. ] TALL_TRUCK = TruckProfile(height_cm=410, gross_weight_kg=35000, axle_count=5) @pytest.mark.parametrize("name,origin,dest", TRAPS) def test_truck_refuses_trap(name, origin, dest): """A constrained truck must NOT be routed through these.""" with pytest.raises(NoRouteError): truck_route(origin, dest, TALL_TRUCK) @pytest.mark.parametrize("name,origin,dest", TRAPS) def test_car_control_routes(name, origin, dest): """Control: a car MUST route. Proves the test exercises the constraint rather than failing for an unrelated reason (bad coords, network, etc).""" car_route(origin, dest) # must not raise ``` Wire these into CI, not a manual QA checklist. A refactor that drops `vehicle[height]` from the params dict is invisible in code review and obvious in a failing test. The car-mode control is not optional. Without it, a test that fails because your coordinates are wrong looks identical to a test that passes because the constraint worked. ## Common mistakes **Setting `transportMode=truck` and stopping there.** The engine is selected. The vehicle is not described. **Passing metres instead of centimetres.** **Counting only the tractor's axles.** `axleCount` includes trailers. **Using deprecated `truck[...]` or `weightLimit`.** **Hardcoding dimensions at the call site.** They belong on the vehicle record. **Treating `NoRouteError` as a failure to retry with relaxed constraints.** That produces an illegal route. **Falling back to car routing.** Worse than failing. **Constraints on the server, absent on the device.** The driver deviates, the phone reroutes, and the constraint set never left your backend. Push the profile to the device. **Assuming `category=lightTruck` waives dimension limits.** It waives certain legal restrictions. Bridges do not care about your category. ## Production considerations **Vehicle profiles are model data.** Store them once. Validate at write time — a vehicle record with a null height should fail before it reaches a routing call. **Truck routing is slower than car routing.** More constraints, larger search space. Size your timeouts. **Confirm entitlement before scoping a pilot.** Truck routing inclusion in the free tier should be verified against your specific account. **For many-to-many truck travel times**, Matrix Routing accepts truck parameters. Do not loop. See [Distance Matrix](/examples/distance-matrix). **For hazmat**, add `shippedHazardousGoods` (an array of cargo types) and `tunnelCategory` (`B`|`C`|`D`|`E`). These are different parameters governing different things. See [Hazmat Routing](/use-cases/hazmat-routing). ## Related The full constraint set and why omitting one is silent. Where the profile lives, and how it reaches the device. Cargo types, tunnel categories, and refusing to route. Constraints belong on records, never at call sites. ## HERE documentation * [Routing API v8 reference](https://docs.here.com/routing/reference/routing-api-v8-calculateroutes) — full `vehicle` parameter table * [Transport modes](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/topics/transport-modes.html) ## Placematic * [Truck Routing](https://placematic.com/here-location-services/truck-routing/) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. We have deployed HERE truck routing into production ELD systems. [Talk to us](https://placematic.com/contact/). # Can I build an ELD platform on HERE? Source: https://docs.placematic.com/faq/can-i-build-an-eld-platform Yes. And reverse geocoding will not survive the audit — only Route Matching returns the road segments a regulator will ask about. # Can I build an ELD platform on HERE? **Short answer:** Yes. Google added commercial vehicle constraints in August 2026, for the contiguous US. It still has no equivalent to Route Matching, and no `MapVersion` in any response — which means no defensible reconstruction of driven segments and no audit artifact. For an ELD platform that is the deciding difference, and it is not a close one. ## The requirement that shapes everything An ELD platform's location layer has one property that distinguishes it from every other fleet system: **a regulator may ask you to prove it.** Not "show me the route." Prove **which road segments** the vehicle travelled, on what date, against **which map**, and why you believe it. [Reverse geocoding](/guides/reverse-geocoding) resolves a coordinate to a nearby address. It cannot tell you which road the vehicle drove, and **it will not survive an audit.** Only Route Matching returns segments with attributes. ## The audit artifact For any trip a regulator might question, you must be able to produce: 1. **The raw trace** — the observations, unmodified 2. **The matched segments** — your reconstruction 3. **The `MapVersion`** used to produce the match 4. **The match timestamp** `MapVersion` is a real field in HERE's Route Matching v8 response. A trip matched in March against map `17116` may match differently in September against a newer map. **Both can be correct.** Without recording which map produced which result, you cannot explain the discrepancy — and you will be asked to. **Matched output is a derivation. Keep the source.** ## Three obligations, three dependencies **Hours of Service.** Drive time comes from motion. But *projected* HOS feasibility depends on the route. Change the route, change the projection. **HOS and routing are coupled.** Most ELD platforms model them as independent modules. A dispatcher who reroutes at 2pm has altered the HOS projection whether or not the system says so. Test that a re-route triggers an HOS recomputation. One line. Catches a class of bug that surfaces during an audit rather than during QA. **IFTA.** Miles per jurisdiction, quarterly, subject to audit. Requires matched segments. **Route planning.** Truck constraints. A route your platform produces that a driver cannot legally drive is a defect with consequences beyond the software. ## Speed compliance Compare observed speed against the **matched segment's** posted limit — not the nearest road's. Nearest-road comparison produces **false violations on frontage roads parallel to highways.** Drivers notice. So do lawyers. ## The cost architecture 200 vehicles at a ten-second ping rate is **648,000 packets per day.** **Nothing expensive touches a GPS packet on arrival.** Not geocoding. Not matching. Not routing. Store it. Detect stops locally. Geocode events. Match trips overnight. Instrument the ratio of reverse-geocode calls to detected stops. It should be near 1. **Evaluate asset-based pricing.** A countable vehicle population with unpredictable call volume is exactly that model's shape. ## Common misconceptions **"Reverse geocoding gives me the route."** It gives you addresses. Audits ask about road segments. **"Nearest-road snapping is good enough."** It fails at interchanges, on divided highways, and on frontage roads — precisely the geometry that generates disputed miles. **"I'll re-match for the report."** Re-matching six months later bills again **and** may produce a different answer against an updated map. Store the result. **"Sampling rate is a detail."** It's the dominant quality input, and it's a device configuration decision made before any API is called. **"Positioning APIs help here."** Hybrid radio positioning produces estimates with uncertainty radii. Feeding them to a map matcher produces confident nonsense. ## Related The full architecture, and the audit artifact. Map matching, confidence, and `MapVersion`. 648,000 packets, \~1,150 API calls. Constraints, units, and trap geometry. **HERE documentation:** [Route Matching v8](https://www.here.com/docs/bundle/route-matching-api-developer-guide/page/README.html) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. We have deployed HERE into production ELD systems. [Talk to us](https://placematic.com/contact/). # Can I run Google and HERE in parallel? Source: https://docs.placematic.com/faq/can-i-run-google-and-here-in-parallel You should, during migration. And you may want to stay that way permanently. # Can I run Google and HERE in parallel? **Short answer:** Yes, and you should — both as a migration technique and, for many products, as the permanent architecture. ## During migration: shadow-write **The primary serves the user. The shadow result is discarded.** The shadow call must be **asynchronous and fire-and-forget.** If it can affect production latency, you have built a second point of failure to test the first. **Feature-flag per endpoint, not per user.** A percentage rollout across a routing engine gives you two populations with different route quality and no way to attribute an incident. A rollback should be one flag, one surface, no deploy. **Compare offline, against ground truth.** Not against each other. Two wrong answers can agree. **Keep both live for at least one full business cycle after cutover.** Two invoices for a month is cheaper than one incident. You're not testing the happy path — you're waiting for the edge case that only occurs on the last Friday of the month. ## Permanently: the hybrid **The best architecture for many products is both.** Google for consumer-facing place search and autocomplete. HERE for routing, matrix operations, batch geocoding, and truck constraints. Nothing requires vendor monogamy, and the split follows the strengths cleanly. **Why:** Google's business POI database — hours, reviews, photos, categories — is categorically better than HERE's. Placematic sells HERE. We'd rather you know now than after a migration. **Why:** HERE expresses the commercial vehicle constraints Google's Large Vehicle Routing does not — weight per axle group, ADR tunnel category, radioactive cargo — and covers 200+ countries against Google's 48 contiguous states. It also returns toll cost for the described vehicle, which Google documents as unsupported. **Document the split as a decision**, in writing, before someone frames a deliberate hybrid as a failed migration. ## What running both costs Two contracts. Two invoices. Two support paths. Two free tiers that don't pool. Real overhead. For a small team it may exceed the benefit. For a fleet platform where the map is customer-facing and the routing is safety-critical, it does not. ## The one thing you must not do **Never fail over from HERE truck routing to Google Large Vehicle Routing.** This got more dangerous in August 2026, not less. An unconstrained fallback produced obviously wrong routes — a truck sent under a low bridge fails loudly. A partially-constrained fallback produces plausible ones. If legality depends on axle-group weight, tunnel category or a cargo class the fallback cannot express, the route looks correct, passes review, and is illegal. **Refuse to route instead.** Assert field-level parity in the facade, not merely that a provider responded. A dispatcher calling operations is a better outcome than a driver following a route through a low bridge. ## What genuinely fails over * **Tiles.** A CDN with a secondary origin. Users see a map. * **Geocoding.** A cached result, or a stale coordinate with a warning. * **Serviceability checks.** Materialized polygons in your own database. Neither vendor being up is relevant. ## Common misconceptions **"A hybrid is a failed migration."** It's a decision. Write it down before someone else characterizes it. **"Dual-running is expensive."** Shadow traffic doubles call volume for its duration. Budget for it, and say so. **"I'll compare Google's answer to HERE's."** Compare both against telematics ground truth. Two wrong answers can agree. **"Percentage rollout is standard practice."** Standard for buttons. Wrong for routing engines. ## Related Shadow-write, per-endpoint flags, tested rollback. The shadow implementation, in code. Where each genuinely wins. Ground truth, distributions, gates. *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Can I store geocoding results? Source: https://docs.placematic.com/faq/can-i-store-geocoding-results A contract question with architectural consequences. Get the answer in writing before you design a permanent cache. # Can I store geocoding results? **Short answer:** It depends on your contract, and you need the answer in writing before you build. **Both HERE and Google place restrictions on retaining geocoded coordinates.** Those restrictions differ by platform, by plan, and over time. We will not summarize them here, because they change and a summary that is wrong is worse than no summary. **Ask your account manager. Get it in writing.** ## Why this is an architecture question, not a legal footnote A permanent geocode cache is the single largest cost lever available in this domain. Buildings are stationary; re-geocoding the same address on every order is pure waste. **But its permissibility is a contract term.** If your contract restricts retention, your entire cost model changes — and so does your migration business case. Resolve this in week one, not after you've built the pipeline. ## What to ask, specifically Not "can we cache?" — the answer is always "it depends." Ask: 1. May we store latitude and longitude against a customer record, indefinitely? 2. May we store the normalized address string returned by the API? 3. May we store the platform's place ID or feature ID? 4. Does the answer change if we display the result on that platform's map versus a competitor's? 5. Does the answer change for batch geocoding versus real-time? 6. What is the retention limit, if any, and does it reset on re-query? ## Personal data, separately A permanent geocode cache is a permanent record of where people live. **Coordinates that identify a residence are personal data** under GDPR and comparable regimes, whether or not a name is attached. Right-to-erasure applies to the cache. It is not exempt because it is "just a cache." **The clean resolution:** separate the *address → coordinate* mapping from the *person → address* mapping. The coordinate for `123 Main Street, Chicago` is a fact about the world. The fact that Jane Smith lives there is personal data. Store them separately. Delete the second. Keep the first. This is defensible, it preserves the cache hit rate, and it's what mature systems do. ## Multi-tenant systems A **shared** geocode cache across tenants is a data leak. Cache hit patterns are a side channel. Response latency reveals whether another tenant has geocoded a given address. Repeated across a candidate list, that reconstructs a customer list. Key every cache by tenant. Verify it with an explicit cross-tenant test in CI. The world-facts / tenant-data split above resolves this too, if your threat model permits it. ## Common misconceptions **"Caching is obviously allowed, everyone does it."** Everyone has a contract. Read yours. **"A cache isn't storage."** It is storage with a TTL. If the TTL is `NULL`, it is a database. **"Erasure requests don't reach the cache."** They do. **"Retention policy is someone else's problem."** A cache that never expires is a retention policy that says "forever." Someone in your organization has committed to something shorter. Reconcile them. ## If retention is restricted You still have levers: * **Deduplicate before geocoding.** Free, always permitted. * **Batch what tolerates latency.** Cheaper per record. * **Debounce autocomplete.** Fewer calls, no storage. * **Materialize derived artifacts** — delivery zones, service polygons — which are your data, not the platform's. ## Related Normalization, invalidation, and the privacy design. The cache key that leaks. What to cache, and for how long. Who answers contract questions. *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. Caching and retention terms are exactly the kind of question a Gold Partner answers before you build. [Talk to us](https://placematic.com/contact/). # Do I buy from HERE or Placematic? Source: https://docs.placematic.com/faq/do-i-buy-from-here-or-placematic Direct contract or Gold Partner. When each makes sense, what a partner contract actually changes, and when you should go direct. # Do I buy from HERE or Placematic? **Short answer:** Depends on your volume and your tolerance for procurement. If you're a large enterprise with a vendor management function and can meet minimum volume commitments comfortably, go direct. Otherwise a Gold Partner is usually simpler. We sell HERE. Read the "buy direct" section carefully. ## What a partner contract changes **One contracting party.** Placematic USA LLC. You never open a HERE account, never receive a HERE invoice, never negotiate with HERE. **Entitlements confirmed before you build.** The `403`-in-week-six problem becomes a conversation in week one. **Pricing tiers.** Gold Partner pricing tiers are not available to direct customers at standard volume levels. Whether that beats your negotiated direct pricing is an empirical question — bring your current terms. **No minimum volume commitment for pilot access.** **Contractual price stability is a term in your contract, not a property of the platform.** HERE pricing through a partner does not reprice mid-term because the contract says so. Negotiate it, or don't claim it. ## Buy direct if * You have a dedicated vendor management function and want the relationship in-house * You can meet minimum volume commitments comfortably * Your legal team prefers a direct agreement with the data provider * **A routing defect traced to map data needs the shortest possible escalation path.** Through a partner, that path is one hop longer. Whether that matters depends on whether a wrong route is an inconvenience or a bridge strike. ## Buy through a Gold Partner if * You want partner pricing tiers * You don't want to negotiate volume minimums * You want one contract, one invoice, and someone accountable for the *integration* working — not just for the API being up * Entitlement and licensing questions should be resolved before your sprint starts * You're embedding HERE in a product you sell, and the **redistribution** question needs answering ## The redistribution question Exposing HERE APIs to *your* customers may constitute redistribution, which carries different licensing terms than internal use. There's a spectrum. **Internal consumption** — your platform calls HERE, renders a map, shows an ETA. **Pass-through** — your API returns HERE's response shape to your customer's engineers. **Redistribution** — your customer builds against what is effectively a HERE proxy. The further right you sit, the more likely you need explicit terms. **Resolve this before you build.** Discovering it during a customer's security review is expensive. This is the single clearest reason to have a Gold Partner conversation early. It's a short conversation when the partner is the contracting party. ## Common misconceptions **"A reseller is a markup."** Gold Partner tiers exist because HERE prices differently at partner volume. Whether that's better *for you* depends on your negotiated direct terms. Bring them. **"Going through a partner means slower support."** It means a different support path. For licensing, entitlement, and integration questions it's faster. For a map data defect, it's one hop longer. **"I can compare on rate cards."** Entry-level rates are close. Call mix, tier placement, batching, and caching determine your bill. See [What drives my API bill?](/faq/what-drives-my-api-bill) ## If you already license HERE directly Bring your current pricing. We'll tell you honestly whether we can improve it. Sometimes the answer is no. ## Related Call volume vs asset-based, and free bundles that don't pool. Multi-tenancy, quota, and the redistribution question in full. Isolation, cost attribution, and the gateway you build regardless. Including what we don't. *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Do I have to change my data model? Source: https://docs.placematic.com/faq/do-i-have-to-change-my-data-model Yes, in one specific way — and it's the way migrations fail. Design your abstraction from the richer capability set. # Do I have to change my data model? **Short answer:** Not much, except in one place that matters enormously. If you build a provider facade, **design its interface from HERE's capability set, not Google's.** ## The failure An interface derived from Google before August 2026 has no field for vehicle height at all. One derived from Google's Large Vehicle Routing has height, weight and axle count — and still no field for weight per axle group, ADR tunnel category, or toll cost. Either way, when you later add HERE, that data **has nowhere to live and gets silently dropped.** The second case is worse, because the interface looks complete. If your Google implementation had no notion of a vehicle's physical dimensions, an interface abstracted from it has no field for one. If it has a partial notion — dimensions and gross weight, but not axle groups — the missing fields are precisely the ones that determine legality in the jurisdictions that regulate most tightly. The route still returns `200`. The truck still gets dispatched. ## What to add **`access` point.** HERE returns two coordinates per geocode: `position` (the address) and `access` (where a vehicle arrives on the road network). Google returns one. **Route to `access`.** Routing to `position` may target a building's geometric centre, or a point separated from the road by a car park. **Per-field confidence.** HERE returns `queryScore` *and* `fieldScore` — which component was uncertain. `queryScore: 0.95` with `fieldScore.houseNumber: 0.4` is a failed delivery. `fieldScore.postalCode: 0.8` is not. **Rooftop vs interpolated.** `houseNumberType: "PA"` is a surveyed point address. `interpolated` is an estimate along a street segment, potentially tens of metres off. **Vehicle profile as a first-class record.** Height (centimetres), gross weight (kilograms), axle count including trailers, hazmat cargo types, ADR tunnel category. **Vehicle constraints belong on the vehicle record, never at a call site.** A developer adding a new endpoint who copies a routing call without the profile has introduced a defect that is invisible in code review and returns `200`. **Provider identifier in a nullable side column.** If cutting over wrote a `here_id` onto a core record, rollback becomes stateful. Keep it separable. ## What doesn't change Your PostGIS geometry. Your delivery zones. Your geocode cache table. Your geofence polygons. **Most of what a location system does never touches a location API.** That layer is identical on either platform. ## Convert units at the adapter Not in the interface. HERE's `height` is centimetres. TomTom's `vehicleHeight` is metres. Store one, and make each adapter responsible for the conversion. Pass `4.1` to HERE and you have declared a four-centimetre truck. It routes under every bridge in North America. The response is `200`. ## Common misconceptions **"We'll add the fields when we need them."** You need them at the moment you dispatch a truck, and by then the interface has fifty call sites. **"Confidence is just a number."** Storing a coordinate without its confidence converts a probabilistic estimate into a fact. Every downstream system treats it as truth. Low-confidence fallbacks silently corrupt revenue maps and route vehicles to centroids. **"The facade should be minimal."** The facade should express every constraint that determines whether a route is legal. ## Related The provider facade, designed correctly. Both adapters, side by side. Constraints live on records. Why `fieldScore` and `houseNumberType` must be persisted. *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Does HERE support truck routing? Source: https://docs.placematic.com/faq/does-here-support-truck-routing Yes — and setting transportMode=truck is not how you use it. Omit the dimensions and HERE routes an unconstrained vehicle without warning. # Does HERE support truck routing? **Short answer:** Yes, and it's the strongest reason to choose HERE. But `transportMode=truck` selects the routing **engine**. It does not tell HERE how tall your vehicle is. Omit the dimensions and you get a route. It returns `200`. It contains a polyline. It may route a 4.1-metre trailer under a 3.4-metre bridge. **HERE will not warn you, because you did not tell it there was a trailer.** ## What HERE expresses Verified against the [Matrix Routing v8 OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi), v8.47.0: * **Dimensions** — `height`, `width`, `length`, `trailerLength` (centimetres) * **Weight** — `grossWeight`, `currentWeight`, `emptyWeight`, `payloadCapacity` (kilograms) * **Axles** — `axleCount`, `weightPerAxle`, and `weightPerAxleGroup` with `single`, `tandem`, `triple`, `quad`, `quint` * **`tunnelCategory`** — ADR restriction code `B`, `C`, `D`, `E` * **`shippedHazardousGoods`** — eleven cargo types * **`kpraLength`** — kingpin-to-rear-axle, for California and Idaho * **`category: lightTruck`** — a legal exemption that does **not** waive physical dimension limits * `trailerCount`, `tiresCount`, `licensePlate` for environmental-zone evaluation **Weight per axle *group* is the decisive parameter for US carriers.** Federal bridge formula and state limits are frequently expressed per axle group, not per axle. A five-axle tractor-trailer with a steer axle, a tandem drive, and a tandem trailer group cannot be described by a single per-axle scalar. Competitors that expose only a scalar `axleWeight` cannot determine route legality in those jurisdictions. ## Units will catch you | Parameter | Unit | A common wrong value | | ---------------------- | --------------- | ------------------------------- | | `vehicle[height]` | **centimetres** | `4.1` → a four-centimetre truck | | `vehicle[grossWeight]` | **kilograms** | `35` → a 35 kg truck | | `vehicle[axleCount]` | count | tractor only, omitting trailers | **Units are not validated for plausibility.** Pass `4.1` and HERE routes a four-centimetre vehicle under every bridge in North America. `200`. **`vehicle` supersedes `truck`.** The `truck` object is deprecated in the current v8 spec. `weightLimit` is deprecated in favour of `grossWeight`. If you're reading a 2021 tutorial, you're reading deprecated parameters. ## Verify it actually applies Route a 410 cm vehicle through the 11foot8 bridge (Durham NC), Storrow Drive (Boston), and the Southern State Parkway (Long Island). **Any path returned is a failure.** Run the same three in `car` mode as a control. **Put this in CI.** A refactor that drops `vehicle[height]` from a params dict is invisible in code review and obvious in a failing test. ## Common misconceptions **"`transportMode=truck` is truck routing."** It selects the engine. Both parts are required. **"`category=lightTruck` waives dimension limits."** It waives certain legal restrictions written for normal trucks. **Bridges do not care about your category.** **"`axleCount` is the tractor's axles."** It's the whole combination, including all trailers. Range 2–255. **"200 means a valid route."** `200` with an empty `routes` array means no path exists. For truck routing, "no legal route" is a **correct** answer. A system that retries with relaxed constraints has just produced an illegal route. **"Google can't do this at all."** It can, since August 2026, in the 48 contiguous United States — dimensions, gross weight, axle count and hazmat classes. What its documentation lists as unsupported is truck toll prices, speed limits and radioactive cargo, and coverage stops at the contiguous 48. Weight per axle group is the parameter to verify against Google's current reference before assuming either way. ## The dangerous pattern Server-side truck routing with constraints, device-side recalculation **without** them. The driver deviates. The phone reroutes. The constraint set never left your backend. **Push vehicle profiles to the device. Test the recalculation path.** ## Related Constraints, units, and validating they apply. Why the toll figure depends on the vehicle profile, and what Google returns instead. Working code, with CI assertions. Where the profile lives, and how it reaches the device. The axle-group difference, sourced. **HERE documentation:** [Routing API v8 reference](https://docs.here.com/routing/reference/routing-api-v8-calculateroutes) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. We have deployed HERE truck routing into production ELD systems. [Talk to us](https://placematic.com/contact/). # How accurate are HERE ETAs? Source: https://docs.placematic.com/faq/how-accurate-are-etas The routing duration is usually fine. Your ETA is wrong because it omits everything the routing engine cannot know. # How accurate are HERE ETAs? **Short answer:** Accurate enough that the routing engine is rarely your problem. **Your ETA is wrong because a routing duration is not a delivery promise.** Presenting a routing duration as an ETA is the most common source of ETA inaccuracy — and it is a **modelling error, not an API error.** It survives migration. ## What the routing engine doesn't know A routing duration is the time a vehicle spends **moving.** That's rarely the number the customer cares about. Missing: * **Preparation time** — food cooking, order picking, loading * **Service time** at the stop — unloading, signature, elevator, the customer who takes four minutes to answer * **Dwell time** between arrival and next departure * **Parking** — eleven minutes in dense urban delivery, modelled by nobody * **Driver behaviour** — breaks, deviations In dense last-mile delivery, service time and failed attempts frequently dominate travel time. ## Decompose it ETA = preparation + travel + service + buffer Store each separately. Present the sum. Instrument the **residual** — actual minus predicted — broken down by component. Most teams discover that travel duration is accurate to within a few percent and **service time is off by 40%.** That tells you where to spend the next quarter. ## Set `departureTime` explicitly Traffic is evaluated at the **departure** time, not at request time. A route computed at 2pm for a 6pm departure inherits 2pm traffic unless you say otherwise. `departureTime=any` disables traffic awareness entirely. ## Don't re-route to refresh Re-routing on every GPS ping to refresh an ETA is the same architectural error as reverse-geocoding every ping. **Recompute the remaining duration from the route geometry and current position.** That's arithmetic on data you already hold. Call routing when the driver **deviates.** ## Communicate uncertainty "Arriving at 3:14pm" is a promise you cannot keep. "Between 3:05 and 3:25" is one you can. **Report the residual distribution — p50, p90, p99 — not the mean.** A platform whose durations are unbiased on average but have twice the variance produces twice as many late deliveries. ## Common misconceptions **"Google's traffic is better, so their ETAs are better."** Google's traffic derives from an enormous consumer device population. That's a real advantage for dense urban passenger routing. **Whether it's an advantage on your corridors, at your departure times, is empirical.** Test it. **"Switching vendors will fix our ETAs."** If service time is your error term, no vendor helps. **"Multi-stop ETAs compound linearly."** They compound at every hop, and dwell time compounds with them. **"A car route's duration is a truck's duration."** It isn't, because it isn't the truck's route. ## Build the feedback loop Actual arrival minus predicted, per job type, per time of day, fed back into service-time estimates. **Without it, your ETA accuracy will never improve — on any platform.** ## Related Decomposition, the feedback loop, and cost traps. Where service time dominates travel time. ETA refresh from geometry, in code. `departureTime` and traffic semantics. *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # How can I reduce location API costs? Source: https://docs.placematic.com/faq/how-can-i-reduce-location-api-costs Seven patterns, ranked by the order of magnitude they move. Most teams start at number seven. # How can I reduce location API costs? **Short answer:** Change the unit of work. Choose the right primitive. Materialize what doesn't move. Everything else is a constant factor. ## Ranked by what they move | Rank | Pattern | Moves | Effort | | ---- | -------------------------- | ------------------- | ------- | | 1 | Change the unit of work | Orders of magnitude | Low | | 2 | Choose the right primitive | Orders of magnitude | Low | | 3 | Precompute and materialize | Unbounded → bounded | Medium | | 4 | Cache | Large constant | Low | | 5 | Deduplicate | Large constant | Trivial | | 6 | Batch | Rate difference | Medium | | 7 | Trim the request | Small constant | Trivial | Teams routinely start at rank 7. Setting `return` fields correctly is worth doing and **will never save a platform whose architecture calls a geocoder per GPS packet.** ## 1. Unit of work Geocode **events**, not packets. Debounce keystrokes at 200–300ms. Match **trips**, not points. Instrument the ratio of API calls to business events. Reverse-geocode calls ÷ detected stops should be near 1. ## 2. Right primitive If you're writing a loop around a routing call, stop. You're building a cost table (Matrix) or an itinerary (Tour Planning). Both exist. Both are cheaper. ## 3. Materialize Drive-time isolines around fixed locations are stable for months. A 400-store network with three delivery bands is **1,200 isoline calls per quarter** — not per session. Compute once. Store in PostGIS. Query with `ST_Contains`, forever, for free. ## 4. Cache Buildings are stationary. **Normalize before hashing.** `123 Main St` and `123 Main Street` are the same address and two cache misses. Teams routinely find half their misses were the same address written three ways. **Do not use a TTL on geocoding.** A thirty-day expiry invalidates a stable rooftop match for a building that has stood since 1904, and does nothing about the subdivision that opened yesterday. Invalidate on map release, low confidence, correction, or a failed delivery. ## 5. Deduplicate A raw order export contains enormous repetition. Geocoding four million rows containing nine hundred thousand distinct addresses bills for four million. One `SELECT DISTINCT`. Free. ## 6. Batch If the result is written to a database rather than rendered to a screen, it should have been batched. Nothing is waiting for a nightly job. ## 7. Trim Set `return` explicitly. Nothing consumes turn-by-turn `instructions` server-side. Put a CDN in front of tiles. Coarsen isoline resolution where nobody sees the difference. ## Common misconceptions **"We need a volume discount."** A discount on an unbounded cost is still unbounded. **"Caching is a small optimization."** It's the difference between paying per order and paying per distinct customer. **"We should migrate to a cheaper vendor."** Fix the call pattern first, on your current platform. Re-measure. A meaningful share of teams find the bill halves without a vendor change — and now you have a clean baseline for the migration decision. ## Related The full taxonomy, with arithmetic. Normalization, invalidation, and the privacy question. Materialization, in practice. The process: instrument, fix, model. *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # How do I get a HERE API key? Source: https://docs.placematic.com/faq/how-do-i-get-a-here-api-key Two paths: self-serve on HERE's portal, or partner-issued with entitlements confirmed for your use case. The difference matters more than the speed. # How do I get a HERE API key? **Short answer:** Sign up on HERE's developer portal, or get credentials issued through a Gold Partner. The self-serve path is faster. The partner path tells you, before you build, whether your key covers the API you're about to depend on. ## The longer version **Self-serve.** Create a HERE platform account, register an app, generate a key. Minutes. You get a key against the free tier. **Raster Tile API v3 requires a HERE *platform* account.** Older HERE *developer* account keys do not work against it. This trips up teams following 2021 tutorials. **Partner-issued.** You describe what you're building — not "we need mapping," but "truck routing for an ELD platform" — and receive keys with the relevant entitlements enabled. ## Why entitlement matters more than speed **Not every HERE API is in the free tier.** Entitlements vary by account type. A team can build a working prototype, present it to leadership, get approval, and only then discover the specific API their product depends on carries different terms in a production contract. **Verify each API you plan to use, individually, before sprint planning.** One `curl` per endpoint. Ten minutes. It will save you a quarter. `200` = valid key, entitled. `401` = wrong key. `403` = right key, **missing entitlement**. Retrying is futile. Learn to tell these apart on day one. Truck routing inclusion in the free tier specifically should be confirmed against your account before you scope a pilot around it. Do not assume from general documentation. ## Common misconceptions **"Free tier is a production sandbox."** It's a rate-limited product boundary. If you exceed it in staging, you'll exceed it in production. **"One key covers web and mobile."** Web tile access and mobile SDK access are **separate entitlements**. Discovering this at app store submission is expensive. **"`403` is a transient error."** It will never succeed on retry. It's a commercial conversation, not a technical one. **"I need my own HERE account to use HERE."** Not if you license through a Gold Partner. Placematic USA LLC becomes the contracting party. ## Do this before you write code ```bash theme={null} # Does this key route trucks? curl -gX GET 'https://router.hereapi.com/v8/routes' \ --data-urlencode 'transportMode=truck' \ --data-urlencode 'origin=42.0641,-88.0509' \ --data-urlencode 'destination=41.5250,-88.0817' \ --data-urlencode 'vehicle[height]=410' \ --data-urlencode "apiKey=${HERE_API_KEY}" -G ``` Repeat for every endpoint in your design. `403` on any of them is information you want in week one, not week six. ## Related Sandbox vs production, entitlements, and confirming coverage. Key handling, rotation without downtime, error semantics. `429` and `403` are different problems. The commercial paths, honestly compared. **HERE documentation:** [Identity and Access Management](https://www.here.com/docs) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # How do I handle rate limits? Source: https://docs.placematic.com/faq/how-do-i-handle-rate-limits 429 backs off. 403 never succeeds. Treating them the same generates support tickets and outages. # How do I handle rate limits? **Short answer:** `429` is transient — back off with jitter. `403` is permanent — it means your key lacks entitlement for that API. They are not the same problem and they do not have the same remedy. ## The table | Status | Meaning | Retry? | Do | | ---------------------- | ---------------------------------- | ------------------------------ | ------------------------------------ | | `401` | Key missing, malformed, revoked | No | Check the header and the secret | | `403` | Key valid, **entitlement missing** | **Never** | Contact your account manager | | `429` | Rate limit exceeded | Yes, backoff + jitter | If persistent, your quota is too low | | `400` | Malformed request | No | Fix the parameters | | `5xx` | Upstream failure | Yes, backoff + circuit breaker | Transient | | `200` + empty `routes` | **No path exists** | **No** | May be the correct answer | **Retrying `403` is patient, well-engineered, and permanently futile.** It generates support tickets. Teams add retry logic around `403` because their HTTP client treats all 4xx uniformly. Read the status code. ## `429` correctly **Exponential backoff with jitter.** Fixed-interval retries from a distributed fleet produce a synchronized thundering herd. **Circuit breakers must reset.** A breaker that opens on the first `429` and never closes turns a throttle into an outage. **A persistent `429` is a commercial signal**, not a code defect. It means your contracted quota does not match your workload. ## Batch API has its own semantics Three responses that look like errors and are not: | Response | Meaning | | ------------------ | -------------------------------------------- | | `429` on job start | Concurrent job limit. **Queue.** Not a fault | | `204` on `/errors` | **Zero errors.** Not a failure | | `404` on `/result` | Job hasn't succeeded yet. Poll status first | Code that treats any non-`200` as failure will log a perfectly successful nightly job as broken, every night, until someone reads the specification. ## In a multi-tenant platform HERE sees **one contract.** Your tenants share its rate limit and don't know each other exists. **Enforce a per-tenant budget at your gateway, before egress.** Not by observing `429`s and reacting — by refusing to send the request. **Two ceilings, and they're different:** * **Rate ceiling** — requests per second per tenant. Prevents a runaway loop. * **Cost ceiling** — spend per tenant per month. A tenant can stay under the rate limit for thirty days and still be your entire invoice. **Reserve headroom.** Don't allocate 100% of your contracted limit across current tenants. **Two queues.** Interactive dispatch drains ahead of bulk onboarding, always. ## Log the status alongside the endpoint `403` on `/tour-planning` and `429` on `/routes` are different conversations — one with your account manager, one with your architect. **Alert on any `403`.** In steady state it should be zero. Any occurrence is a licensing issue, not a bug. ## Common misconceptions **"429 means my code is wrong."** It means your quota doesn't match your workload. **"403 will succeed eventually."** Never. **"I'll parallelize to go faster."** More workers against a contract limit produce more `429`s. **"200 means success."** HERE returns `200` with an empty `routes` array and a `notice` when no path exists. `resp.ok` swallows it. ## Related The full error table, rotation, and key handling. Per-tenant quota and the shared rate limit. Retry policy by status code, and circuit breakers that reset. `204` is not an error. *** Need production HERE API keys or implementation support? Placematic is an official HERE # How do I test before production? Source: https://docs.placematic.com/faq/how-do-i-test-before-production Trap geometry, in CI, with a control. And verify every entitlement with one curl before sprint planning. # How do I test before production? **Short answer:** Verify entitlements with `curl` before you write code. Then assert, in CI, that a constrained truck is **refused** on known trap geometry — with a car-mode control proving the test exercises the constraint. ## Step 1 — entitlement, before sprint planning One `curl` per endpoint you plan to use. Ten minutes. ```bash theme={null} curl -gX GET 'https://router.hereapi.com/v8/routes' \ --data-urlencode 'transportMode=truck' \ --data-urlencode 'origin=42.0641,-88.0509' \ --data-urlencode 'destination=41.5250,-88.0817' \ --data-urlencode 'vehicle[height]=410' \ --data-urlencode "apiKey=${HERE_API_KEY}" -G ``` `200` = entitled. `401` = wrong key. `403` = **right key, missing entitlement.** A `403` in week one is information. A `403` in week six is a schedule failure and a credibility failure. ## Step 2 — trap geometry, in CI **Do not ship truck routing without this test.** It is the reason bridge strikes make the news. Route a 410 cm vehicle through these. **Any path returned is a failure.** | Location | Why | | -------------------------------------- | ---------------------------------------------- | | 11foot8 bridge, Durham NC | Clearance well below 4.1 m | | Storrow Drive, Boston MA | Commercial vehicles prohibited; low overpasses | | Southern State Parkway, Long Island NY | Commercial vehicles prohibited | ```python theme={null} @pytest.mark.parametrize("name,origin,dest", TRAPS) def test_truck_refused(name, origin, dest): with pytest.raises(NoRouteError): truck_route(origin, dest, TALL_TRUCK) @pytest.mark.parametrize("name,origin,dest", TRAPS) def test_car_control_routes(name, origin, dest): """A car MUST route. Proves the test exercises the constraint, not a bad coordinate or a network failure.""" car_route(origin, dest) # must not raise ``` **The car-mode control is not optional.** Without it, a test that fails because your coordinates are wrong looks identical to a test that passes because the constraint worked. **Wire this into CI, not a manual QA checklist.** A refactor that drops `vehicle[height]` from a params dict is invisible in code review and obvious in a failing test. ## Step 3 — watch the units `vehicle[height]` is **centimetres**. `vehicle[grossWeight]` is **kilograms**. Pass `4.1` and you have declared a four-centimetre truck. It routes under every bridge in North America. The response is `200`. Units are not validated for plausibility. Validate them yourself. ## Step 4 — validate against ground truth, not against a competitor For routing quality: take 500 real historical trips with telematics actuals. Route them. Compare against **what the vehicle actually did**, not against another platform. Two wrong answers can agree. **Report the residual distribution — median, p90, p99 — not the mean.** A platform whose durations are unbiased on average but have twice the variance produces twice as many late deliveries. ## What a sandbox key is and isn't **Sandbox and production keys are separate credentials.** Same endpoints, different quotas. Never ship a sandbox key. **A free tier is not a pilot.** A free tier is a rate-limited product boundary. A pilot is a commercial arrangement scoped to your use case, with the APIs you actually need enabled. ## Common misconceptions **"I'll test truck routing manually before launch."** The constraint that silently disappeared in a refactor is the one that ends up in an incident report. **"Two points in a parking lot proved the constraint works."** Synthetic coordinates route under any constraint set. Test on geometry designed to reject you. **"200 means the route is valid."** `200` with an empty `routes` array means no path exists. For truck and hazmat routing, that's a correct answer. **"I'll validate on the free tier."** Confirm the free tier includes the API you're validating. It may not. ## Related Constraints, units, and the trap geometry in detail. Working code, with the CI assertions. Ground truth, distributions, and shadow comparison. Entitlements, and why `403` is a commercial conversation. *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # How do I validate the migration? Source: https://docs.placematic.com/faq/how-do-i-validate-the-migration Against ground truth, not against each other. Report distributions, not means. And for truck routing, the comparison is a gate, not a metric. # How do I validate the migration? **Short answer:** Replay real production traffic through both platforms, compare each against **ground truth**, and report the distribution of the error — not the mean. **Do not compare the providers against each other.** Two wrong answers can agree. ## Why error rates won't tell you The failure modes are subtle and delayed. A truck route that omits a constraint returns `200` and looks correct in a diff. A silently transposed matrix produces plausible travel times and wrong assignments. A geocode that falls back to a city centroid renders as a pin on a map. **Everything succeeds.** You will not notice the regression by watching error rates. The most dangerous migration outcome is not an outage. It is a system that works, is cheaper, and is quietly wrong in ways that surface as operational incidents six weeks later, with no clear cause. ## What to compare against | Surface | Ground truth | | ---------------- | --------------------------------------------------- | | Route duration | Actual driven duration, from telematics | | Route distance | Actual driven distance | | Route geometry | Actual driven path, map-matched | | Geocode | A held-out set with verified rooftop coordinates | | Matrix | Sampled cells, validated against single-route calls | | **Truck routes** | **Trap geometry — pass/fail** | ## Report distributions, not means ```sql theme={null} SELECT provider, percentile_cont(0.50) WITHIN GROUP (ORDER BY error) AS p50, percentile_cont(0.90) WITHIN GROUP (ORDER BY error) AS p90, percentile_cont(0.99) WITHIN GROUP (ORDER BY error) AS p99 FROM comparison GROUP BY provider; ``` A platform whose durations are unbiased **on average** but have twice the variance produces twice as many late deliveries. The mean will tell you nothing about that. **p99 is where failed deliveries live.** ## Measure confidence calibration The metric almost nobody measures, and the one that determines data quality. **Among results the platform scored above 0.9, what fraction were actually correct?** A geocoder with a *lower* match rate and well-calibrated confidence is more useful than one with a higher match rate and confidence that doesn't predict correctness. The first lets you route uncertain records to an exception queue. The second silently corrupts your database. **Do not port your confidence threshold across platforms.** `queryScore: 0.9` does not mean what `partial_match: false` means. Recalibrate against your own ground truth. ## Truck routing is a gate, not a metric Route a 410 cm vehicle through the 11foot8 bridge (Durham NC), Storrow Drive (Boston), and the Southern State Parkway (Long Island). **Any path returned is a failure.** Run the same three in `car` mode as a control. All three must route — proving the test exercises the constraint rather than failing for an unrelated reason. **Keep these assertions in CI permanently**, not just during migration. A refactor that drops `vehicle[height]` is invisible in code review. Run the gate against both platforms. Since August 2026 both can pass it, and passing it does not mean the constraint sets are equivalent — it means neither will send a tall vehicle under a low bridge. Toll cost, axle-group weight and coverage are separate questions the trap geometry does not ask. ## Test the rollback **A migration you cannot reverse is not a migration. It is a bet.** Write the rollback procedure. Then execute it once in production, deliberately, during a low-traffic window. Flip the flag. Verify traffic moves. Flip it back. An untested rollback is a hypothesis. If rollback takes twenty minutes, nobody will pull the trigger during an incident. They will debug forward, badly, at 2am. ## Common misconceptions **"Cost validation is enough."** A cheaper wrong route is worse than an expensive right one. **"We'll compare the two providers."** Against ground truth. Two wrong answers agree. **"The mean error looks fine."** Report p90 and p99. **"We'll delete the trap tests after cutover."** They're the thing that catches the refactor two years from now. ## Related Shadow comparison, per-endpoint flags, tested rollback. Entitlement checks and trap geometry. How to benchmark on your own address dataset. The trap geometry, in detail. *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # How does HERE billing work? Source: https://docs.placematic.com/faq/how-does-billing-work Per-transaction, per-API, with two commercial models. Free bundles don't pool, and choosing the wrong model punishes operational diligence. # How does HERE billing work? **Short answer:** Usage-based, metered per API, under one of two commercial models. There is no flat monthly fee and no cross-API bundle. ## Every API is a separate meter Routing, geocoding, tiles, matrix, tour planning, traffic. Six meters. **Free bundles are per-API and do not pool.** Exhausting your routing bundle does not consume your geocoding bundle, and unused geocoding does not subsidize routing. ## Two commercial models Priced per API call. Cost scales with usage. Suits SaaS platforms whose call volume tracks customer count. Priced per tracked asset per month. Cost scales with fleet size, not API chatter. Suits fleet operators with countable vehicles. The choice is not cosmetic. A dispatcher who reroutes a 200-truck fleet forty times a day generates enormous call volume against a stable asset count. **Under call-volume pricing that team pays for operational diligence.** Under asset-based pricing they don't. **Rule of thumb:** if you can count your vehicles and can't predict your calls, ask about asset-based. If you can predict your calls and don't own vehicles, call volume. **Asset-based pricing availability depends on contract tier.** Confirm it before it becomes load-bearing in a business case presented to your CFO. ## Base Plan vs Enterprise Plan **Base Plan** — transaction-based, with free monthly bundles. You pay above the bundle. **Enterprise Plan** — cost certainty through volume-based and asset-based pricing. Includes fleet optimization SDK, HERE Navigate SDK, offline maps. The jump is not primarily about price per call. It's about what you're **entitled to**. Offline maps and the Navigate SDK are not purchasable as Base Plan add-ons. HERE documents **Tour Planning as included in the HERE Base Plan.** Confirm your own entitlement — Base Plan inclusion and your contract's inclusion are separate facts. ## What counts as a transaction One billable API call. Definitions vary by product — a tile request, a route calculation, a geocode. **Confirm the unit per API before forecasting.** Tiles in particular may be metered per request or per session depending on whether you use web tiles, the mobile SDK, or static maps. Two implementations rendering the same view can bill differently. ## Who invoices you Through Placematic: **Placematic USA LLC, exclusively.** One invoice covering all HERE services. HERE does not invoice you. Direct: HERE invoices you. ## Common misconceptions **"There's a bundle."** There isn't. Six APIs, six meters. **"Free bundles pool."** They don't. **"Batch and real-time are the same rate."** They're not. Batch geocoding is cheaper per record. **"Contract length doesn't matter."** Longer commitments buy lower per-call pricing. **"Growth means proportional cost."** Once the architecture is right, most surfaces are bounded by location count or distinct address count — business numbers you know in advance. ## Related Both models, free bundles, and how to model a defensible bill. Why nobody can answer without your call mix. Cost attribution, and why billing unit must correlate with cost unit. Contracting paths. **Placematic:** [HERE API pricing](https://placematic.com/here-location-services/here-pricing/) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # How does HERE handle hazmat routing? Source: https://docs.placematic.com/faq/how-does-here-handle-hazmat Two separate parameters governing different things. Confusing them is the most common — and most consequential — hazmat integration error. # How does HERE handle hazmat routing? **Short answer:** Two parameters, and they are **not** the same thing. * **`shippedHazardousGoods`** — an **array of cargo types** * **`tunnelCategory`** — the **ADR tunnel restriction code** `B`, `C`, `D`, or `E` Supplying one does not imply the other. Confusing them is the most common hazmat integration error, and this is a domain where a software defect is a regulatory event. ## The values **`shippedHazardousGoods`** accepts an array from: `explosive`, `gas`, `flammable`, `combustible`, `organic`, `poison`, `radioactive`, `corrosive`, `poisonousInhalation`, `harmfulToWater`, `other` **`tunnelCategory`** accepts a single value: `B`, `C`, `D`, or `E`. It governs which tunnels the vehicle may enter under ADR. Verified against the [Matrix Routing v8 OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi), v8.47.0, July 2026. **Physical constraints still apply.** A hazmat tanker is also a tall, heavy vehicle. Both constraint sets are required. ## Do not map a boolean `shippedHazardousGoods` is not `is_hazmat`. It's an array of eleven cargo types. Map your internal material classification against HERE's enum **deliberately**, with a documented mapping table, and **have it reviewed by someone qualified in the applicable regulation.** This is not a place to guess. ## Your system must be able to refuse A routing request for a hazmat load without a class is not a request to route with defaults. **It is an invalid request.** Fail loudly. **An empty `routes` array on a hazmat request is not a bug.** It may be the correct answer: no permissible route exists between these points for this class. A system that retries with relaxed constraints, or falls back to car routing, has just produced an illegal route. ## Constraints belong to the load, not the carrier The same tractor hauls dry van on Monday and hazmat class D on Tuesday. `shippedHazardousGoods` is a property of **what is in the trailer**, and it changes per shipment. Vehicle dimensions belong to the vehicle record. Never hardcode either at a call site. ## Cache keys must include the constraint set An origin-destination cache key is a defect. Do not serve a route computed for one cargo class to a shipment carrying another. ## Record the map release Restrictions change. A route computed in March against a March map may differ in September. If an incident occurs, you will be asked what the system knew and when. ## Common misconceptions **"`B|C|D|E` are hazmat classes."** They're tunnel categories. Different parameter, different meaning. **"`category=lightTruck` exempts hazmat."** It waives certain legal restrictions written for normal trucks. **It does not make a flammable liquid non-flammable.** **"If no route is found, relax a constraint."** That is how illegal routes reach drivers. **"Google can do this."** It offers no hazmat routing capability. Not a close comparison. **"Regulatory data is uniform globally."** Coverage and depth of hazmat restriction data vary by market. ## Test it Ask your compliance officer for known-prohibited segments in your operating region. Assert that a routed hazmat vehicle avoids each. Put it in CI. This is worth more than any amount of documentation review. ## Related The full architecture, and refusing to route. Physical constraints, which also apply. Map release versioning as an audit artifact. Both implement ADR tunnel codes. The taxonomies differ. **HERE documentation:** [Routing API v8 reference](https://docs.here.com/routing/reference/routing-api-v8-calculateroutes) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # How hard is it to migrate from Google Maps? Source: https://docs.placematic.com/faq/how-hard-is-it-to-migrate-from-google Varies enormously by surface. Batch geocoding is a sprint. Rendering is a rewrite. Migrate one at a time. # How hard is it to migrate from Google Maps? **Short answer:** Depends entirely on which surface. Batch geocoding is often a sprint. Rendering, if you're on the Google Maps JS SDK, is a rewrite of that layer. Migrate one surface. Stabilize. Then the next. ## By surface, ascending | Surface | Effort | Why | | ---------------------------- | ----------- | ------------------------------------------------------------------------ | | **Batch geocoding** | Low | No UI. If it breaks, you fix a nightly job. Largest saving | | Telematics reverse geocoding | Low | Backend, high volume, no user-facing risk | | Matrix operations | Medium | Flat row-major arrays. Async job lifecycle | | Routing | Medium–high | Quality validation required, not just cost | | Truck routing | Medium–high | New surface since Aug 2026. Unit conversion plus constraint-tail mapping | | Autocomplete | Medium | User-facing. Be honest about quality | | Rendering | High | Most visible. Tile aesthetics differ. Migrate last | | Mobile SDK | High | Separate entitlement. Binary size. Offline strategy | **Never migrate two surfaces simultaneously.** Two independent changes, one production incident, ambiguous root cause. You will spend more on the investigation than you saved that month. ## But optimize first Migrating an uncached, undebounced implementation to a cheaper provider **moves the waste to a cheaper meter.** You will save money and still be wrong. Cache geocoding. Batch what tolerates latency. Debounce autocomplete. Deduplicate. Stop reverse-geocoding every GPS ping. CDN in front of tiles. Re-measure. A meaningful share of teams find the bill halves without a vendor change. **What remains is your real migration case.** ## The mechanical differences that bite **Coordinate order.** HERE Routing takes `lat,lng`. GeoJSON is `lng,lat`. Once, at 2am. **Country codes.** Google uses ISO alpha-2 (`US`). HERE uses alpha-3 (`USA`). A naive port silently filters to nothing. **`200` is not success.** HERE returns HTTP `200` with an empty `routes` array and a `notice` when no path exists. **`403` ≠ `401`.** Valid key, missing entitlement. Never retry. **Vehicle units differ.** HERE's `vehicle[height]` is centimetres. Google's large vehicle fields are named in millimetres — `totalHeightMm`. A factor-of-ten error produces either a vehicle that routes nowhere or a 41-centimetre vehicle that routes everywhere and returns `200`. Convert at the adapter, assert the converted value in a test. **Matrix results are flat, row-major.** Index wrong and you get a silently transposed matrix — plausible times, wrong assignments, nothing throws. **Batch is a job lifecycle**, not a bulk endpoint. **Confidence scores are not comparable.** `queryScore: 0.9` does not mean what `partial_match: false` means. Recalibrate against your own ground truth. **Do not port the threshold.** ## The hidden cost **Migration engineering time is a real line item.** If the payback period exceeds eighteen months, the savings argument is weaker than the headline number. **Say so before your CFO does.** It costs you one migration and buys credibility for the ones worth doing. ## Common misconceptions **"It's a config change."** `google.maps.*` in your React components is not a config change. **"We should migrate everything."** Usually not. Routing and batch geocoding to HERE, consumer place search stays on Google, is a common and sensible endpoint. **"The response schemas are similar."** They're mechanically different throughout. Not hard work. Not zero. **"Tile styling is cosmetic."** It is, until the product review. Show design the maps before you commit. ## Related Dual-running, shadow comparison, rollback. Endpoint mapping and sequencing. A real geocoding migration, request by request. Optimize first. You may not need to migrate. *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # How much does HERE cost? Source: https://docs.placematic.com/faq/how-much-does-here-cost Nobody can answer this without knowing your call mix. Here's what determines it, and how to produce a number you can defend. # How much does HERE cost? **Short answer:** Nobody — including us — can answer this without knowing which APIs you call, how many times, in which region, under which contract, and whether you batch and cache. Those variables span more than an order of magnitude. Anyone giving you a number without asking is guessing. **We do not publish a savings percentage.** Cost depends on API mix, monthly volume, region, contract terms, billing SKU, batching, and architecture. A single number claiming to summarize all of those is not a claim. It's a slogan. ## What determines it **Which APIs.** Every API is a separate meter. Routing, geocoding, tiles, matrix, tour planning. No bundle. **How many calls.** Not how many *orders* or *vehicles* — how many calls. These diverge wildly. A 200-truck fleet at a 10-second ping rate produces 648,000 packets a day. Whether that's 648,000 API calls or 1,150 is an architecture decision. **Volume tier.** Rates drop as volume rises. Routing sees the steepest curve. **Free bundles.** Per-API. They do not pool. Exhausting routing does not consume geocoding. **Which commercial model.** Call volume, or asset-based (per tracked vehicle per month). Asset-based pricing availability depends on contract tier. Confirm it before it becomes load-bearing in a business case presented to your CFO. **Whether you batched.** Batch geocoding is cheaper per record than real-time. If the result is written to a database rather than rendered to a screen, it should have been batched. **Whether you cached.** Buildings are stationary. A team with a 12% geocode cache hit rate does not have a pricing problem. ## How to get a defensible number 1. **Instrument.** Per-endpoint call counts, per month, **from logs.** Not estimates. 2. **Classify by latency tolerance.** What must be real-time? What could run overnight? 3. **Identify what's cacheable.** 4. **Separate matrix workloads from routing workloads.** If you loop routing calls to build a distance table, you have a matrix workload whichever API you currently use. 5. **Model both commercial models** against your worst month. 6. **Then compare vendors.** Not before. The [pricing calculator](https://placematic.com/here-location-services/here-pricing/) produces a range from volume and API type. It is a planning tool, not a quote. ## Common misconceptions **"I can estimate from my Google bill."** No. Google bills per session where HERE bills per request, and the reverse. **The meters do not map one to one.** **"Per-1,000 rates tell me what I'll pay."** Entry rates are close across vendors. Your bill is set by call mix, tier, batching, and bundles. **"Growth means a proportionally bigger bill."** It shouldn't. Once the architecture is right, most surfaces are bounded by business numbers you already know: location count, distinct addresses, trips per day. If a line item is large and *variable*, something is calling an API where it should be reading a table. **"The free bundle scales."** It's a marketing boundary, not an architectural guarantee. ## The forecast you want > "Our location cost is $X per thousand new distinct addresses, plus $Y per store per quarter." That's a sentence a CFO can plan against. > "It was \$40k last month." That isn't. ## Related Both commercial models, and how to model your real bill. The unit of work, not the rate card. Seven patterns, ranked by the magnitude they move. Instrument, fix, then decide whether to migrate. **Placematic:** [HERE API pricing](https://placematic.com/here-location-services/here-pricing/) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # FAQ Source: https://docs.placematic.com/faq/index Direct answers to the questions engineers actually ask before choosing HERE Location Services — including the ones where the answer is 'it depends on your contract.' # FAQ Short answers. Where a topic needs depth, we link to it rather than repeating it. **Some answers depend on your contract, not on the platform.** Pricing, entitlements, service limits, caching rights, and redistribution terms vary by plan, region, and agreement. Where that is true, we say so rather than presenting one plan's behaviour as universal. If an answer here contradicts your contract, your contract is right. ## Getting started A platform of \~two dozen APIs, priced separately, with different maturity levels. Two paths. One is fast and unentitled; one is scoped to what you'll ship. Direct, or through a Gold Partner. Honest trade-offs both ways. Choose by the question your system asks, not by product name. ## Pricing Depends on API mix, volume, region, contract, and whether you batched. Not the rate card. The unit of work. Seven patterns, ranked by the order of magnitude they move. Sometimes. Usually not for the reason you think. Call volume vs asset-based. Free bundles that don't pool. ## Technical If you're writing a loop, you already know the answer. Almost always. Buildings are stationary. A contract question with architectural consequences. Get it in writing. `429` backs off. `403` never succeeds. They are not the same problem. Trap geometry, in CI, with a control. ## Migration Varies by surface. Geocoding is a sprint. Rendering is a rewrite. Yes, in one specific way. It's the way migrations fail. You should. And you may want to stay that way. Against ground truth, not against each other. ## Fleet and logistics Yes — and setting `transportMode=truck` is not how you use it. Cargo type and tunnel category are different parameters. The routing duration is usually fine. Your ETA is wrong for other reasons. Yes. Reverse geocoding will not survive the audit. ## Licensing and support Contracting, entitlements, and implementation. Not a support tier. Including several cases where you shouldn't. ## The four questions behind most of these If you read nothing else: **What is the unit of work?** A GPS packet is not an event. A keystroke is not a query. An address is not an order. Systems bill on the unit they call, and the default unit is nearly always too small. **Is this computation or lookup?** A drive-time polygon is a computation, done quarterly. Testing whether a point is inside it is a lookup, done millions of times, in your own database, for free. **What is the latency budget, honestly?** Nothing is waiting for a nightly geocoding job. **Where does the constraint live?** A truck's height is a property of the truck. Constraints that live in request builders get dropped in refactors, silently, and the response still returns `200`. See [Architecture](/architecture). *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Is HERE cheaper than Google Maps? Source: https://docs.placematic.com/faq/is-here-cheaper-than-google Sometimes. Rarely for the reason you think, and never in a way a single percentage captures. # Is HERE cheaper than Google Maps? **Short answer:** At production volume, often. At low volume, the difference is negligible and not worth a migration. And in the case that matters most — commercial vehicle routing — it isn't a price comparison at all. **We will not publish a savings percentage.** Cost depends on API mix, volume, region, contract terms, billing SKU, batching, and architecture. Those span more than an order of magnitude. Anyone quoting you a single percentage is quoting a benchmark on a workload that isn't yours. ## Where the difference is real **Matrix operations.** If you loop routing calls to build a distance table, the gap is not a rate difference. It is architectural. A 20×500 problem is 10,000 calls or 1. **Batch geocoding at volume.** Cheaper per record, and the workload is usually enormous. \*\*Truck routing — and this changed in August 2026.\*\*On HERE, a truck route is a routing request: same rate, same free allowance as a car route. The vehicle profile does not move you to a different meter. Google's Large Vehicle Routing requires the traffic-aware optimal routing preference, which bills above its Essentials rate — so on Google, describing your vehicle costs more per call. It remains a capability argument as well. Google documents truck toll prices, speed limits and radioactive cargo as unsupported, and coverage stops at the 48 contiguous states. But it is now a cost argument too, which it was not before. ## Where it isn't **Entry-level rates.** Close across vendors. Comparing them teaches you nothing. **Consumer place discovery.** Google's business database — hours, reviews, photos, categories — is categorically better. Cheaper on a feature people stop using is not cheaper. **Low volume.** Under roughly \$1,000/month after optimization, the payback period on migration engineering is measured in years. Do something else. ## The meters do not translate Google bills autocomplete on a session-token model where sessions are used. HERE bills per request. Google prices some products per element; HERE per transaction. **You cannot derive a HERE forecast from a Google invoice.** Instrument per-endpoint call counts from logs first. ## Do this before you compare **Optimize on Google.** Cache geocoding. Batch what tolerates latency. Debounce autocomplete. Deduplicate. Stop reverse-geocoding every GPS ping. Put a CDN in front of tiles. Re-measure. A meaningful share of teams find the bill halves. What remains is your real migration case, with a clean baseline. Present that saving to finance **before** proposing a migration. You'll have delivered a result, and the migration conversation becomes optional rather than defensive. ## And model migration cost | Line | Source | | ------------------------------------- | ------------------ | | Current spend, per endpoint | Logs | | Savings from optimization on Google | Measured | | Remaining spend — the real target | Derived | | Projected HERE cost, same call volume | Both models | | Migration engineering cost | Estimated honestly | | **Payback period** | Derived | **If the payback period exceeds eighteen months, say so before your CFO does.** It costs you one migration and buys credibility for the ones worth doing. ## Common misconceptions **"HERE is 40% cheaper."** Under what call mix, at what volume, in which region, on which contract? **"Price is why we'd switch."** For commercial vehicle routing it is now partly a price argument — Google bills the truck profile above its Essentials rate, HERE does not — but coverage and toll cost are the reasons that survive a change to anyone's rate card. **"Google repriced once, so HERE is safer."** Price stability is a term in your contract, not a property of a platform. Negotiate it or don't claim it. ## Related Platform-level fit, including where Google wins. Optimize first. You may not need to migrate. Constraint scope, coverage, and which SKU your traffic lands on. Not the rate card. *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Should I use Routing or Matrix? Source: https://docs.placematic.com/faq/should-i-use-routing-or-matrix If you're writing a loop, you already know. One rule catches almost every error. # Should I use Routing or Matrix? **Short answer:** Routing returns a **path**. Matrix returns a **cost table**. If you're discarding the path and reading only `duration`, you asked the wrong question. ## One rule **If you are writing a loop around a routing call, stop.** You are building either a cost table (Matrix) or an itinerary (Tour Planning). Both exist. Both are cheaper. ## The arithmetic An n×m cost table costs **n·m routing calls** or **1 matrix call**. A 20-depot, 500-stop assignment problem is **10,000 calls or 1.** That's a complexity difference, not a rate difference. **No pricing negotiation closes it.** ## Where the loop hides * Nearest-driver assignment — one call per available driver * Store locator ranking — one per candidate store * Territory design — one per rep-base × unit centroid * Delivery zone economics — one per zone per candidate * Freight rating — one per lane, per quote, uncached * Site selection — one per demand cell per candidate **All matrices.** ## Why the mistake is systemic, not careless **Routing is discoverable first.** It's what you reach for when you need a travel time. **The loop works.** Correct answers. No error, no warning, no degradation. **The failure is asymptotic.** At five stops it's imperceptible. Growth rates are invisible in a code review of the diff that introduced them. **Matrix is async at scale.** Submit-poll-retrieve is more code and more state. Engineers optimizing for the shortest path to a green test choose synchronous. **The response shape is unfamiliar.** Flat, row-major arrays — `travelTimes: [73, 1231, 983, 400]` for a 2×2 — not nested objects. None of these are unreasonable. Naming the pattern explicitly in your architecture review is worth more than any amount of documentation. ## The inverse error **Matrix returns no geometry.** If a driver follows it, you needed Routing. ## Size ceilings vary by mode There is no single "HERE matrix size limit." | Mode | Sync | Async | Live traffic + custom options | | ---------------- | ------- | ------------- | ----------------------------- | | Flexible | 15×100 | 15×100 | Yes | | Region (≤400 km) | 500×500 | 10,000×10,000 | Yes | | Profile | 500×500 | 10,000×10,000 | **No** | **A truck-constrained matrix with live traffic runs in Flexible mode and caps at 15 × 100.** Read the [spec](https://matrix.router.hereapi.com/v8/openapi) before architecting against any headline figure. ## Common misconceptions **"I'll parallelize the loop."** You've converted a cost problem into a `429` problem. **"Matrix is only for huge problems."** A 1×10 store ranking is one matrix call instead of ten routing calls. The ratio holds at every scale. **"The flat array is just a format detail."** Index it wrong and you get a **silently transposed matrix**. Every travel time is plausible. Every assignment is wrong. Nothing throws. ## Related The full decision tree, and the indexing helper you write once. Modes, async lifecycle, per-cell error codes. Working code, with the indexing helper. Where the matrix feeds the solver. **HERE documentation:** [Matrix Routing v8 OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # What drives my API bill? Source: https://docs.placematic.com/faq/what-drives-my-api-bill Not the rate card. The unit of work you attached the API call to. # What drives my API bill? **Short answer:** The unit of work. Systems bill on the unit they call, and the default unit is nearly always too small. ## The ratios that matter | Wrong unit | Right unit | Typical ratio | | ---------- | ---------------- | ------------- | | GPS packet | Detected stop | \~250 : 1 | | Keystroke | Debounced query | \~8 : 1 | | Order | Distinct address | \~5 : 1 | | Trip point | Trip | \~2,000 : 1 | | Page load | User action | Unbounded | A vehicle emitting a position every ten seconds over a nine-hour shift produces 3,240 packets. Across 200 vehicles: **648,000 per day.** Any API call attached to a packet multiplies by that number. The address is needed when a human looks at a screen, or when a stop is detected. Not at 3:47:20am on I-80. ## The second driver: wrong primitive An n×m cost table costs **n·m routing calls or 1 matrix call.** A 20-depot, 500-stop assignment problem is 10,000 calls or 1. That's a complexity difference, not a rate difference. **No pricing negotiation closes it.** Where the loop hides: nearest-driver assignment, store locator ranking, territory design, freight rating, site selection, delivery zone economics. All matrices. ## The third: computation vs lookup A drive-time polygon is a computation. Testing whether a point is inside it is a lookup. The first belongs to a routing API, computed quarterly. The second belongs to your database, executed millions of times, for free. If your checkout serviceability check calls an isoline API, you have built a spatial database with an API bill attached. ## The diagnostic Instrument **API calls per business event**, per endpoint. * Reverse-geocode calls ÷ detected stops → should be near 1 * Routing calls ÷ dispatches → should be near the number of legs * Geocodes ÷ orders → should approach zero Total volume grows with the business. **The ratio should not.** A ratio that doubles overnight is a regression a deploy introduced, and it's visible hours before it appears on an invoice. Alert on the ratio, not the volume. ## Common misconceptions **"We need a better rate."** You're not paying too much per call. You're making the wrong number of calls. **"It's the tiles."** Put a CDN in front of them and find out. It's usually geocoding, reverse geocoding, or a routing loop. **"Volume tiers will fix it."** A discount on an unbounded cost is still an unbounded cost. **"Migrating will fix it."** Migrating an uncached, undebounced implementation moves the waste to a cheaper meter. You will save money and still be wrong. ## Related Seven patterns, ranked. Patterns 1 and 2 move exponents; the rest move constants. The loop that looks correct and costs orders of magnitude. 648,000 packets. \~1,150 API calls. The same arithmetic, in code. *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # What is HERE Location Services? Source: https://docs.placematic.com/faq/what-is-here-location-services A platform of roughly two dozen APIs — routing, geocoding, maps, positioning, fleet optimization — priced separately and with different maturity levels. # What is HERE Location Services? **Short answer:** A set of REST APIs and SDKs for geocoding, routing, map rendering, positioning, and fleet optimization. Not one API — roughly two dozen, priced separately, with meaningfully different maturity levels. ## The longer version Most teams arrive for one of two reasons: they need truck routing that respects physical vehicle constraints, or their Google Maps bill grew faster than their revenue. Both are good reasons. Neither is a reason to migrate everything. Grouped by what you'd build with them: | Family | APIs | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | **Routing** | Routing v8, Matrix Routing v8, Isoline Routing v8, Route Matching v8, Tour Planning v3 | | **Search** | Geocoding & Search v7 (`/geocode`, `/revgeocode`, `/autocomplete`, `/autosuggest`, `/discover`, `/browse`, `/lookup`), Batch API v7 | | **Rendering** | Raster Tile v3, HERE Maps API for JavaScript, HERE SDK (iOS/Android) | | **Data** | Traffic v7, EV Charge Points v3, Positioning | **Every API is a separate meter.** There is no bundle. Routing, geocoding, and tiles are three different bills. Free bundles are per-API and do not pool. ## What actually differentiates it Price is why teams *start* evaluating HERE. **Routing correctness is why they stay.** HERE Routing v8 accepts vehicle height, width, length, gross weight, current weight, weight per axle, weight per **axle group** (single, tandem, triple, quad, quint), axle count, trailer count, ADR tunnel category, eleven hazardous cargo types, and KPRA length. Google Maps Platform does not expose these, and it does not expose the underlying road attributes either — so you cannot implement them yourself. It also has **Route Matching**, which turns a GPS trace into the road segments actually driven, with a `MapVersion` in the response. That is what IFTA jurisdiction miles and defensible speed compliance require. ## Common misconceptions **"HERE Maps and HERE Location Services are the same thing."** No. HERE Maps is the consumer product. Location Services is the developer platform. Only the latter is licensable. **"It's a cheaper Google Maps."** For business POIs, hours, reviews, and photos, Google's data is **categorically** better. Placematic sells HERE. We'd rather you know now. **"`transportMode=truck` gives me truck routing."** It selects the engine. It does not describe your vehicle. Omit the dimensions and HERE routes an unconstrained vehicle, returns `200`, and does not warn you. **"Tour Planning is enterprise-tier."** HERE documents it as included in the Base Plan. Confirm your own entitlement — Base Plan inclusion and your contract's inclusion are separate facts. **"Matrix has a single size limit."** It doesn't. Ceilings vary by mode: Flexible caps at 15×100 with live traffic; Region async reaches 10,000×10,000 within a 400 km diameter. Read the [spec](https://matrix.router.hereapi.com/v8/openapi). ## When not to use it * Consumer place discovery. Keep Google. * Under \~10k calls/month. You're optimizing a rounding error. * Street View. No equivalent exists. * You need the exact Google tile aesthetic. Your designers will notice. ## Related The full platform overview. A decision tree by use case. Where each genuinely wins. The differentiator, in detail. **HERE documentation:** [HERE Technologies documentation](https://www.here.com/docs) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # What support does Placematic provide? Source: https://docs.placematic.com/faq/what-support-does-placematic-provide Contracting, entitlements, and implementation guidance. Not a support tier, and not a substitute for HERE's own escalation path on map data. # What support does Placematic provide? **Short answer:** We are the contracting party, we confirm entitlements, and we help you architect the integration. We are not a 24/7 NOC, and for a map data defect our escalation path is one hop longer than yours would be with a direct contract. ## What we do **Contracting.** Placematic USA LLC is the contracting and billing party. One contract, one invoice. You never open a HERE account. **Entitlement scoping.** You tell us what you're building — "truck routing for an ELD platform," not "we need mapping" — and we enable the right products. This is where the `403`-in-week-six problem gets solved in week one. **Redistribution and licensing.** If you're embedding HERE into a product you sell, the redistribution question needs answering before you build. A Gold Partner is the right party for that conversation, and it's short when the partner holds the contract. **Implementation guidance.** Architecture review, cost modelling, migration sequencing. We've deployed HERE into production ELD systems, and much of what's in these docs came from doing it badly the first time. **Cost evaluation.** Instrument your call counts, model both commercial models, and produce a number you can defend. See [Cost Reduction Audit](https://placematic.com/here-location-services/cost-reduction-audit/). ## What we don't do **We are not a support tier for the HERE platform.** If HERE has an outage, we're reading the same status page you are. **We do not shorten the escalation path for map data defects.** If a route is wrong because the underlying map attribute is wrong, that's HERE's data, and a partner contract adds a hop rather than removing one. If that latency matters — if a wrong route is a bridge strike rather than an inconvenience — **that's a genuine argument for a direct contract.** **We don't write your integration.** These docs exist so you don't need us to. **We don't guarantee savings.** Cost depends on your call mix, volume, region, contract, and architecture. Anyone quoting a percentage without seeing your logs is guessing. ## What good engagement looks like **Week one, not week six.** Bring the entitlement questions before sprint planning. Ten minutes of `curl` against every endpoint in your design. **Bring your logs.** Per-endpoint call counts, from production, not estimates. Every useful conversation about cost starts there. **Bring your current contract**, if you already license HERE directly. We'll tell you honestly whether we can improve it. Sometimes the answer is no. ## The honest framing Most of what determines whether your location platform succeeds is not the vendor. It's whether you materialized your delivery zones, cached your geocodes, and stopped reverse-geocoding every GPS packet. **None of that requires us**, and all of it is documented here. We are useful for the parts that are commercial: entitlements, contracts, redistribution, and pricing tiers you cannot access directly. ## Related Including several cases where you shouldn't. Both paths, honestly compared. The kind of question we answer before you build. Most of your savings do not require a vendor conversation. *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # When should I cache results? Source: https://docs.placematic.com/faq/when-should-i-cache-results Almost always. Buildings are stationary. But cache the right thing, keyed the right way, invalidated on events rather than clocks. # When should I cache results? **Short answer:** Geocoding, always and permanently. Route geometry, for hours. ETAs, barely. Isolines, materialize them into a database and stop calling the API. ## What to cache, and for how long | Artifact | Stability | Strategy | | --------------------------------------- | ---------------------- | ----------------------------------------- | | Geocode (address → coordinate) | Years | Permanent. Invalidate on events, not time | | Reverse geocode (coordinate → address) | Years | Round coordinate to \~5dp, then cache | | Route **geometry** between fixed points | Hours | Cache | | Route **ETA** | Minutes | Don't. Recompute from geometry | | Isoline polygon | Months | Materialize into PostGIS | | Matrix (fixed depots, fixed stops) | Days–weeks | Hash the input set | | Lane distance (freight rating) | Months | A table, not an API | | Tiles | Static per z/x/y/style | CDN | **Cache geometry and ETAs separately.** The path between two fixed points is stable for hours. The duration is not. One TTL for both serves stale arrival times to drivers. ## Normalization is the multiplier `123 Main St` and `123 Main Street` are the same address and two cache misses. Before hashing: trim, case-fold, expand abbreviations, canonicalize postal codes, normalize unit designators. Instrument cache hit rate **before and after** normalization. Teams routinely find half their misses were the same address written three ways. That's a free doubling with no API involved. ## Don't use a TTL on geocoding A thirty-day TTL invalidates a stable rooftop match for a building that has stood since 1904, and does nothing about the subdivision that opened yesterday. **Time-based TTL on geocoding is cargo-cult caching.** It costs money and improves nothing. Invalidate on **events**: * Map release ships * Confidence score below threshold * Customer corrects their address * A delivery fails to that coordinate Store `geocoded_at` and the map release so that *selective* re-geocoding is possible. Without them your only options are "everything" and "nothing." ## Round coordinates before caching reverse geocodes GPS jitter defeats an exact-match cache. Round to \~5 decimal places — about one metre. A vehicle idling at a depot generates hundreds of pings within a metre of each other. **One cache entry.** ## Common misconceptions **"Caching is a small optimization."** It's the difference between paying per order and paying per distinct customer. A team with a 12% geocode hit rate does not have a pricing problem. **"Redis is the cache."** Redis is a latency optimization. **The durable database record is the system of record.** An eviction is a re-geocode you already paid for. **"I should cache in the browser."** No. Coordinates for customer addresses are not the browser's business, and a client cache is neither durable nor deletable. **"A shared cache across tenants is fine."** It's a side channel. Cache hit patterns and timing reveal whether another tenant has geocoded a given address. Key by tenant. ## And check your contract **Retention of geocoded coordinates is a contract term**, not an engineering decision. Both HERE and Google place restrictions, and they vary by plan and over time. Get the answer in writing before you design a permanent cache. See [Can I store geocoding results?](/faq/can-i-store-geocoding-results) ## Related Normalization, invalidation, Redis vs database, and privacy. The contract question. Pattern 4, in context. Materializing isolines instead of caching them. *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # When should I contact Placematic? Source: https://docs.placematic.com/faq/when-should-i-contact-placematic Before you scope the sprint, not after. And several cases where you shouldn't bother. # When should I contact Placematic? **Short answer:** When the question is commercial — entitlements, contracts, redistribution, pricing tiers. Not when the question is architectural, because those answers are in these docs. ## Contact us before **You scope a sprint around an API.** Ten minutes confirming entitlement beats a quarter discovering a `403`. **You build a business case on asset-based pricing.** Availability depends on contract tier. Confirm before it's load-bearing in front of your CFO. **You embed HERE into a product you sell.** The redistribution question carries different licensing terms than internal use. Resolve it before you ship, not during a customer's security review. **You design a permanent geocode cache.** Retention of geocoded coordinates is a contract term. Get it in writing. **You plan a mobile release.** Web tile access and mobile SDK access are separate entitlements. Discovering this at app store submission is expensive. **You want a cost evaluation.** Bring per-endpoint call counts from your logs. Not estimates. **You already license HERE directly** and want to know whether a partner contract improves your terms. Bring the contract. ## Don't contact us for Most of what determines whether your location platform succeeds does not require a vendor conversation. **"How do I reduce my bill?"** Cache geocoding. Batch what tolerates latency. Debounce autocomplete. Deduplicate. Stop reverse-geocoding GPS packets. Replace routing loops with matrix calls. Do that first, re-measure, and then the conversation is worth having. See [Cost Optimization Patterns](/architecture/cost-optimization-patterns). **"Should I use Routing or Matrix?"** If you're writing a loop, you already know. See [Routing vs Matrix](/architecture/choosing-routing-vs-matrix). **"Is HERE better than Google?"** For consumer place discovery, no. Categorically. See [HERE vs Google Maps](/comparisons/here-vs-google-maps). **"Should I self-host OSM instead?"** Maybe. Test whether the truck restriction tags exist in your operating region first. It takes an afternoon and it frequently ends the discussion. See [HERE vs OpenStreetMap](/comparisons/here-vs-openstreetmap). **"Is my architecture right?"** Read [Architecture](/architecture). If it still isn't clear, then ask. ## And genuinely don't migrate if **Your spend is under roughly \$1,000/month after optimization.** The payback period on migration engineering is measured in years. Do something else. **Your product's core value is consumer place discovery.** Keep Google. **You have no engineering capacity this quarter.** A half-finished migration is worse than either endpoint. **The only argument is "cheaper."** Cheaper on a feature that regresses is not cheaper. We sell HERE. If we thought a migration would fail, telling you now costs us one deal and buys credibility for the ones worth doing. ## What to bring * **Per-endpoint call counts**, from logs * **What you're building**, specifically — vehicle class, geography, volume * **Your current contract**, if you have one * **Your worst month**, not your average ## Related Including what we don't. Do this before you call anyone. Entitlement checks you can run yourself, today. Including when to go direct. *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/), or run the [Cost Reduction Audit](https://placematic.com/here-location-services/cost-reduction-audit/) first. # Which HERE API should I use? Source: https://docs.placematic.com/faq/which-here-api-should-i-use Choose by the question your system asks, not by product name. Routing, Matrix, and Tour Planning look interchangeable and are not. # Which HERE API should I use? **Short answer:** Ask what question your system needs answered. The product name will follow. Choosing by product name leads teams to the wrong product, because Routing, Matrix Routing, and Tour Planning all return travel times. ## The decision path Answer in order. Stop at the first match. **1. Moving a vehicle from A to B?** → **Routing v8**. If it's a truck, van over 3.5t, hazmat carrier, or oversized load, `transportMode=truck` **with** the physical constraints. **2. Travel times between many origins and many destinations?** → **Matrix Routing v8**. Not N routing calls. This is the highest-leverage correct choice on this page. **3. Many stops, optimal order, with constraints?** → **Tour Planning v3**. Time windows, capacity, driver shifts, skills. A solver, not a router. **4. Address → coordinates?** → **`/geocode`** for real-time. **Batch API v7** for anything that tolerates latency. **5. Coordinates → address?** → **`/revgeocode`**, or **`POST /multi-revgeocode`** for a list. **6. Type-ahead address entry?** → **`/autocomplete`**. Not `/autosuggest` — that's for places and misspellings. **7. Free-text place search?** → **`/discover`** (ranked by relevance) or **`/browse`** (ranked by distance). **8. Display a map?** → **Raster Tile v3**. Works with Leaflet, OpenLayers, MapLibre GL. **9. Everywhere reachable in N minutes?** → **Isoline Routing v8**. Compute once, store the polygon. **10. GPS trace → road segments?** → **Route Matching v8**. Essential for IFTA and speed compliance. **11. Route with charging stops?** → **EV routing** — a capability within Routing v8, not a separate service. **12. Location where GPS is unreliable?** → **Positioning**. Most teams never need this. ## The distinction that costs the most | | Routing | Matrix | Tour Planning | | ------------- | ------------------------- | ------------------------------------- | -------------------------------------- | | **Question** | How do I get from A to B? | What are the times between all these? | What order should I visit these stops? | | **Output** | A path | A table | An itinerary | | **Calls** | 1 per route | 1 per matrix | 1 per optimization | | **Wrong use** | Building a matrix from it | Building an itinerary from it | Using it for A→B | **If you find yourself writing a loop around the Routing API, stop.** You are building either a matrix or an optimizer. Both exist. Both are cheaper than your loop. A 20×500 problem is 10,000 routing calls or 1 matrix call. ## Common misconceptions **"`/geocode` can find restaurants."** It resolves addresses. Place search is `/discover`. **"`/autocomplete` and `/autosuggest` are the same."** Autocomplete completes *addresses*. Autosuggest handles misspellings and suggests *places*. Wrong choice produces a type-ahead that feels broken to users and correct to engineers. **"Reverse geocoding tells me which road the vehicle drove."** It returns a nearby address. Only Route Matching returns segments, and only Route Matching survives an audit. **"Isolines answer 'which store serves this customer.'"** Two stores' 20-minute polygons overlap. Assign by travel time from a matrix. Isolines define coverage; matrix defines assignment. **"I have to pick one vendor for everything."** No. Keep Google for consumer place discovery. Move routing and batch geocoding to HERE. That's a decision, not a compromise. ## Related The full decision tree, by use case. Why engineers write the loop, and how to catch it. The short version. Seven endpoints, one correct choice per job. **HERE documentation:** [HERE Technologies documentation](https://www.here.com/docs) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. [Talk to us](https://placematic.com/contact/). # Authentication Source: https://docs.placematic.com/getting-started/authentication How to authenticate against HERE APIs — API keys vs OAuth, key rotation, error codes that matter, and the security mistakes that end up in incident reports. HERE APIs support two authentication mechanisms: API keys and OAuth 2.0 bearer tokens. Most REST endpoints accept an API key. Some services — and some enterprise contracts — require OAuth. Through Placematic, you authenticate with an `apiKey` header. No direct HERE account is created. ## Why this matters Authentication is where two categories of production incident originate. The first is a leaked credential. An API key committed to a public repository, embedded in a mobile binary, or pasted into a browser bundle. Someone else's traffic, your invoice. The second is quieter: **misreading error codes.** `401`, `403`, and `429` are three different failures with three different remedies. Teams that treat them uniformly build retry loops around permanent errors, hammer rate limits into hard blocks, and open support tickets for entitlement problems. Neither is a hard engineering problem. Both are routine. ## When should you use each mechanism? Server-to-server calls, backend services, batch jobs, most REST endpoints. Simple, stateless, and sufficient for the overwhelming majority of integrations. Required for certain services and enterprise contracts. Short-lived tokens, appropriate where credential lifetime is a security requirement, or where an organization mandates token-based access. If you don't know which you need, you need an API key. OAuth requirements announce themselves in the endpoint documentation or in a security review, not in general guidance. ## Key concepts **An API key authenticates the application, not a user.** There is no per-user identity. Access control is your responsibility, in your application layer. **Keys are scoped to entitlements.** A key valid for geocoding may return `403` on tour planning. That is a licensing boundary, not a bug. See [Getting a HERE API Key](/getting-started/getting-a-here-api-key). **Sandbox and production keys are separate credentials.** Different quotas, same endpoints. Never promote a sandbox key. **Keys do not expire on their own.** They persist until revoked. This is convenient and it is the reason rotation policy matters. **OAuth tokens are short-lived.** Obtain a token, cache it until near expiry, refresh. Do not request a new token per API call — you will rate-limit yourself against the token endpoint before you rate-limit against the API. ## How do I authenticate a request? ```bash cURL theme={null} curl "https://router.hereapi.com/v8/routes" \ -H "apiKey: $PLACEMATIC_KEY" \ -G \ --data-urlencode "transportMode=truck" \ --data-urlencode "origin=42.0641,-88.0509" \ --data-urlencode "destination=41.5250,-88.0817" \ --data-urlencode "vehicle[height]=410" \ --data-urlencode "return=summary" ``` ```python Python theme={null} import os import requests SESSION = requests.Session() SESSION.headers.update({"apiKey": os.environ["PLACEMATIC_KEY"]}) def route(origin: str, destination: str) -> dict: resp = SESSION.get( "https://router.hereapi.com/v8/routes", params={ "transportMode": "truck", "origin": origin, "destination": destination, "vehicle[height]": 410, "return": "summary", }, timeout=10, ) if resp.status_code == 403: raise PermissionError("Key valid, entitlement missing — do not retry") if resp.status_code == 429: raise RuntimeError("Rate limited — back off, then retry") resp.raise_for_status() return resp.json() ``` ```javascript Node.js theme={null} const KEY = process.env.PLACEMATIC_KEY; async function route(origin, destination) { const params = new URLSearchParams({ transportMode: "truck", origin, destination, "vehicle[height]": "410", return: "summary", }); const resp = await fetch( `https://router.hereapi.com/v8/routes?${params}`, { headers: { "apiKey": KEY } } ); if (resp.status === 403) { throw new Error("Entitlement missing — retrying will not help"); } if (resp.status === 429) { throw new Error("Rate limited — apply exponential backoff"); } if (!resp.ok) throw new Error(`HERE returned ${resp.status}`); return resp.json(); } ``` Never place the key in a query string. URLs are logged by proxies, load balancers, CDNs, and browser history. Use the header. ## What do the error codes mean? This table is the most useful thing on this page. | Status | Meaning | Retry? | What to do | | ------ | ---------------------------------- | ----------------- | ---------------------------------------------------------------------------------------------- | | `401` | Key missing, malformed, or revoked | No | Check the header name and the secret. Confirm the key wasn't rotated. | | `403` | Key valid, entitlement missing | **Never** | You are not licensed for this API. Contact Placematic. Retrying is a support ticket generator. | | `429` | Rate limit exceeded | Yes, with backoff | Exponential backoff with jitter. If persistent, your quota is too low for your workload. | | `400` | Malformed request | No | Fix the parameters. Often a missing truck constraint or bad coordinate order. | | `5xx` | Upstream failure | Yes, with backoff | Transient. Retry with backoff and a circuit breaker. | Log the status code alongside the endpoint. `403` on `/tour-planning` and `429` on `/routes` are different conversations — one with your account manager, one with your architect. ## Best practices * **Store keys in a secret manager.** AWS Secrets Manager, Vault, GCP Secret Manager. Not `.env` in git. Not Slack. Not a shared Postman collection. * **One key per environment.** Development, staging, production. Rotating a compromised dev key should never cause a production outage. * **Proxy browser traffic through your backend.** Any key in frontend JavaScript is public. If you must expose one, use a restricted key you're comfortable losing. * **Restrict where the platform permits.** Domain restrictions on browser keys, IP allowlists on server keys. * **Rotate on a schedule and on personnel change.** Someone who read the production key still has it after they leave. * **Support two live keys during rotation.** Issue the new key, deploy, verify, then revoke the old one. Never revoke first. * **Cache OAuth tokens.** Refresh near expiry, not per request. * **Handle `429` with exponential backoff and jitter.** Fixed-interval retries from a distributed fleet produce a synchronized thundering herd. * **Never retry `403`.** It will not succeed. Ever. ## Common mistakes **Committing the key to version control.** Rotate immediately. Git history is not a secret store — removing the file does not remove the commit. Assume the key is compromised. **Embedding the key in a mobile app binary.** Binaries are trivially decompiled. Proxy through your backend. **Retrying `403` with exponential backoff.** Patient, well-engineered, and permanently futile. Read the status code. **Requesting a fresh OAuth token per call.** You will exhaust the token endpoint's rate limit long before the API's. **Using one key across all environments.** A leaked dev key now requires a production rotation. A staging load test now consumes production quota. **Revoking the old key before deploying the new one.** Rotation with an outage attached. Run both keys in parallel until traffic confirms the cutover. **Treating `429` as a permanent failure.** Circuit breakers that open on the first rate-limit response and never reset turn a throttle into an outage. ## FAQ **Do I need my own HERE account to authenticate?** No. Placematic issues keys scoped to your account. Placematic USA LLC is the contracting and billing party. **API key or OAuth — which should I use?** API key, unless a specific endpoint or your security policy requires OAuth. Most integrations never need it. **Where do I put the key?** The `apiKey` request header. Not the query string. **Can I use one key for sandbox and production?** No. They are separate credentials with separate quotas. **What do I do if a key leaks?** Rotate immediately. Issue a new key, deploy, verify traffic, revoke the old one. Then audit git history and any shared collections. **How do I rotate without downtime?** Run two valid keys simultaneously. Deploy the new one, confirm traffic has moved, then revoke. **Why am I getting `403` on an endpoint that works elsewhere?** Entitlement. Your account is not licensed for that API. This is a commercial conversation, not a technical one. **Do API keys expire?** Not automatically. They persist until revoked — which is precisely why you should have a rotation policy. ## Related guides Sandbox vs production keys and confirming entitlements. Which entitlements to request before you hit your first `403`. Rate limits and quotas are a commercial parameter, not just a technical one. Platform overview. Also relevant: [Truck Routing Guide](/guides/truck-routing) *** Need production HERE API keys or enterprise licensing? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate costs, migrate from Google Maps and build production-ready geospatial solutions. # Choosing the Right HERE APIs Source: https://docs.placematic.com/getting-started/choosing-the-right-here-apis A decision guide for selecting HERE APIs by use case — routing vs matrix vs tour planning, real-time vs batch geocoding, and the expensive mistakes teams make choosing wrong. Choosing HERE APIs by product name leads teams to the wrong product. Choose by the question your system needs answered. "How long is the drive from A to B?" and "What is the drive time between these 200 depots and these 3,000 stops?" look like the same question. They are not the same product, they are not the same price, and building the second out of the first is one of the most expensive architectural mistakes in this domain. ## Why this matters The cost of choosing the wrong API is not a slightly higher invoice. It's an architecture you have to unwind. A team approximates multi-stop optimization with N routing calls. It works at 5 stops. At 40 stops it takes ninety seconds, costs eight times what tour planning would, and produces routes a human dispatcher would reject. By the time this is obvious, it's load-bearing. A team routes trucks with the car engine because the response shape was familiar. Everything looks fine in QA — QA doesn't drive under bridges. The most expensive API decisions are made in week one, by engineers optimizing for the fastest path to a green test, and discovered in month six by operations. ## How do I choose the right HERE API? Answer these in order. Stop at the first match. **1. Are you moving a vehicle from A to B?** → **Routing API.** If the vehicle is a truck, van over 3.5t, hazmat carrier, or oversized load, use `transportMode=truck` **with** the physical constraints. See [Truck Routing Guide](/guides/truck-routing). **2. Do you need travel times between many origins and many destinations?** → **Distance Matrix API.** Not N routing calls. One request. This is the highest-leverage correct choice on this page. **3. Do you need to visit many stops in an optimal order, with constraints?** → **Tour Planning API.** Time windows, vehicle capacity, driver shifts, skills. This is a solver, not a router. Enterprise tier, typically committed plan. **4. Do you need to convert an address to coordinates?** → **Geocoding API** for real-time, one at a time. **Batch Geocoding** for anything that tolerates latency. If you are geocoding a file, you want batch. See [HERE Pricing Explained](/getting-started/here-pricing-explained). **5. Coordinates back to an address?** → **Reverse Geocoding API.** Common in telematics — turning a GPS ping into a human-readable location. **6. Type-ahead address entry in a UI?** → **Autocomplete / Search API.** Be candid about whether Google's POI database is better for your users. For street addresses, HERE is strong. For "coffee shop near me," it is not. **7. Do you need to display a map?** → **Digital Maps / tiles.** Works with Leaflet, OpenLayers, MapLibre GL, and the HERE Maps SDK. Web tiles and mobile SDK are separate entitlements. **8. Do you need to match a raw GPS trace to the road network?** → **Route Matching API.** Turning noisy probe data into travelled road segments. Essential for ELD, IFTA reporting, and anything auditing where a vehicle actually went. **9. Do you need EV range and charging stops in the route?** → **EV Routing.** Consumption model, current charge state, charging stops as routing outputs. Not a filter on top of car routing. **10. Do you need location where GPS is unreliable?** → **Positioning API.** Hybrid GPS/Wi-Fi/cellular. Warehouses, urban canyons, indoors. Most teams never need this. ## Routing vs Matrix vs Tour Planning This distinction earns its own section because getting it wrong is expensive. | | Routing | Distance Matrix | Tour Planning | | ------------- | ------------------------------ | -------------------------------------------- | -------------------------------------- | | **Question** | How do I get from A to B? | What are the times between all these points? | What order should I visit these stops? | | **Input** | Origin, destination, waypoints | N origins, M destinations | Stops, vehicles, constraints | | **Output** | A path | A matrix of times/distances | An optimized itinerary | | **Calls** | 1 per route | 1 per matrix | 1 per optimization | | **Wrong use** | Building a matrix from it | Building an itinerary from it | Using it for A→B | | **Tier** | Standard | Standard | Enterprise / committed | **If you find yourself writing a loop around the Routing API, stop.** You are either building a matrix (use Distance Matrix) or building an optimizer (use Tour Planning). Both exist. Both are cheaper than your loop. Matrix operations support large origin-destination sets in a single call, with different limits depending on whether you call synchronously or asynchronously. Check the current limits in the [official HERE documentation](https://www.here.com/docs) before you architect around a specific ceiling — and confirm which mode your entitlement covers. ## Real-time vs batch geocoding Same data, different product, materially different price. **Use real-time geocoding when** a user is waiting. Address entry at checkout. Dispatcher looking up a customer. **Use batch geocoding when** nothing is waiting. Nightly address normalization. Backfilling a customer table. Importing a partner's location file. The tell: if the result is written to a database rather than rendered to a screen, it should have been batched. **And cache either way.** Addresses do not move. Re-geocoding the same customer address on every order is the most common avoidable cost in this entire platform. ## Key concepts **Transport mode selects an engine, not a speed profile.** `truck` changes traversable segments and legal turns. It is not `car` with a penalty applied. **Constraints are inputs, not filters.** Height, gross weight, axle count, hazmat class. Omit them and the truck engine assumes an unconstrained vehicle. No warning is issued. **Entitlements are per-API.** Requesting the right API means little if your key isn't licensed for it. You will discover this as a `403`. See [Authentication](/getting-started/authentication). **Batch is asynchronous.** Submit, poll, retrieve. Design for it rather than wrapping it in a synchronous facade with a timeout. **You do not have to choose one vendor for everything.** Keep Google for consumer place search, move routing and batch geocoding to HERE. This is a common and defensible architecture. ## Best practices * **Write down the question before you pick the product.** One sentence per integration point. This step catches most wrong choices for free. * **Prototype against the exact API you will ship.** Not the adjacent one with the friendlier response shape. * **Test truck routing on known trap geometry.** The 11foot8 bridge in Durham NC. Storrow Drive in Boston. The Southern State Parkway on Long Island. If your integration routes a 4.1m vehicle through any of these, your constraints aren't applied. * **Confirm entitlements before sprint planning.** One `curl` per intended endpoint. Ten minutes. * **Separate what needs latency from what needs to be cheap.** Then route each to the right product. * **Cache geocoding results in your own datastore.** Not in a request-scoped memo. * **Reconsider quarterly.** Products you dismissed at 10k calls/month may be obvious at 500k. ## Common mistakes **Looping the Routing API to build a distance matrix.** Slower, more expensive, more failure surface. Distance Matrix exists. **Looping the Routing API to build an optimized tour.** Worse routes than the solver, at higher cost. Tour Planning exists. **Using car routing for commercial vehicles.** The response is well-formed. The route is unsafe. **Using real-time geocoding for batch workloads.** Paying for latency you are throwing away. **Choosing Autocomplete because it's cheaper than Google, when the use case is POI discovery.** Your users will notice. Cost savings on a feature people stop using is not savings. **Approximating Route Matching with nearest-road lookups.** Fine until an auditor asks which road segment the vehicle actually travelled. **Migrating every API at once.** Two systems, two failure surfaces, one incident with no clear cause. Migrate one. Stabilize. Then the next. ## FAQ **Which HERE API should I use for truck routing?** Routing API with `transportMode=truck` and explicit physical constraints. Both parts are required. See [Truck Routing Guide](/guides/truck-routing). **What is the difference between Routing and Distance Matrix?** Routing returns a path between two points. Distance Matrix returns times and distances across many origin-destination pairs in one call. If you're looping routing calls, you want matrix. **When do I need Tour Planning instead of Distance Matrix?** When you need an ordered itinerary respecting constraints — time windows, vehicle capacity, driver hours — not just a table of travel times. **Is HERE Autocomplete as good as Google Places?** For street addresses, competitive. For business POIs, reviews, and hours, no. Choose per use case, not per vendor. **Can I mix HERE and Google in one product?** Yes, and many teams should. Google for consumer place search, HERE for routing and batch geocoding is a sensible split. **Do I need the mobile SDK or will the tile API do?** Tiles for web. SDK for native turn-by-turn, offline maps, or on-device rendering. Separate entitlements, both available under one Placematic contract. **How do I know if my key covers an API?** Call it. `200` means entitled. `403` means licensed elsewhere, not here. See [Authentication](/getting-started/authentication). **Where do I find full parameter documentation?** The [official HERE documentation](https://www.here.com/docs). These guides cover implementation judgement, not parameter reference — we don't duplicate what HERE maintains well. ## Related guides Constraints, trap geometry, and validating that your routes are drivable. Choosing the right API is the largest cost lever you have. Per-API comparison, including where Google wins. Also relevant: [Authentication](/getting-started/authentication) · [Batch Geocoding](/guides/batch-geocoding) · [Migrating from Google Maps](/guides/google-migration) *** Full catalog of HERE services available through Placematic: [View all HERE Location Services](https://placematic.com/here-location-services/) Need production HERE API keys or enterprise licensing? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate costs, migrate from Google Maps and build production-ready geospatial solutions. # Getting a HERE API Key Source: https://docs.placematic.com/getting-started/getting-a-here-api-key How to get a HERE API key — sandbox vs production, what the free tier actually covers, and the entitlement questions to resolve before you start building. There are two ways to get a HERE API key: sign up on HERE's developer portal, or get credentials issued through a Gold Partner. The self-serve path takes minutes and gives you a key against the free tier. The partner path takes one business day and gives you keys scoped to your actual use case, with entitlements confirmed in advance. Which one you want depends entirely on whether you are exploring or building. ## Why this matters The free tier is excellent for exploration and dangerous for planning. Not every HERE API is included in it. Entitlements vary by account type. A team can build a working prototype, present it to leadership, get approval, and only then discover that the specific API their product depends on carries different terms in a production contract. **Confirm entitlement before you build, not after.** Truck routing inclusion in the free tier in particular should be verified against your specific account before you scope a pilot around it. Do not assume from documentation that it is available to you. The failure mode is not a technical one. It is a schedule and credibility failure — six weeks of engineering committed to an architecture that requires a commercial conversation nobody planned for. ## When should you use each path? Use when: you are evaluating feasibility, writing a spike, or testing whether an API returns the shape of data you expect. Low commitment, fast, sufficient. Use when: you are building something that will ship. You need entitlement certainty, production quotas, contractual pricing, and someone accountable when it breaks. Concretely — go partner-issued if any of these apply: * Your architecture depends on truck routing, tour planning, or matrix operations * You need production-volume rate limits, not free-tier throttles * Your finance team needs a forecastable line item * Your security review requires a data processing agreement * You want the migration cost from Google modelled before you commit engineers ## Key concepts **Sandbox and production keys are separate credentials.** Both are scoped to your account. Sandbox keys let you build against real endpoints with non-production quotas. Never ship a sandbox key. **API keys authenticate the application, not the user.** They belong in server-side environment variables. See [Authentication](/getting-started/authentication) for the full treatment, including when you need OAuth instead. **Keys are scoped to entitlements.** A key that works for geocoding may return `401` or `403` on tour planning. This is not a bug. It means your account does not include that product. **Free tier ≠ pilot.** A free tier is a rate-limited product boundary. A pilot is a commercial arrangement scoped to your use case, with the APIs you actually need enabled, and no volume minimum. They are not substitutes. Through Placematic, pilot access does not auto-convert to a paid contract. If HERE turns out to be the wrong fit for your use case, we say so. That is the point of a scoped pilot rather than a generic sandbox. ## How do I get an API key through Placematic? Three steps, one business day: 1. **Tell us what you're building.** Not "we need mapping." Specifically: truck routing for an ELD platform, batch geocoding for address normalization, matrix routing for depot optimization. The use case determines which entitlements we enable. 2. **We scope and issue.** You receive sandbox and production keys, both tied to your account, with the relevant APIs enabled. No direct HERE account is created. No credit card. 3. **You build.** A solutions engineer is available during the pilot. OpenAPI spec and Postman collection are provided. No demo call is required to get a key. If you want one, it's twenty minutes. ## Your first authenticated request Once you have a key, verify entitlement before writing any application code: ```bash cURL theme={null} curl "https://router.hereapi.com/v8/routes" \ -H "apiKey: $PLACEMATIC_KEY" \ -G \ --data-urlencode "transportMode=truck" \ --data-urlencode "origin=42.0641,-88.0509" \ --data-urlencode "destination=41.5250,-88.0817" \ --data-urlencode "vehicle[height]=410" \ --data-urlencode "vehicle[grossWeight]=35000" \ --data-urlencode "return=summary" ``` ```python Python theme={null} import os import requests resp = requests.get( "https://router.hereapi.com/v8/routes", headers={"apiKey": os.environ["PLACEMATIC_KEY"]}, params={ "transportMode": "truck", "origin": "42.0641,-88.0509", "destination": "41.5250,-88.0817", "vehicle[height]": 410, "vehicle[grossWeight]": 35000, "return": "summary", }, timeout=10, ) resp.raise_for_status() print(resp.json()) ``` ```javascript Node.js theme={null} const params = new URLSearchParams({ transportMode: "truck", origin: "42.0641,-88.0509", destination: "41.5250,-88.0817", "vehicle[height]": "410", "vehicle[grossWeight]": "35000", return: "summary", }); const resp = await fetch( `https://router.hereapi.com/v8/routes?${params}`, { headers: { "apiKey": process.env.PLACEMATIC_KEY } } ); if (!resp.ok) throw new Error(`HERE returned ${resp.status}`); console.log(await resp.json()); ``` A `200` means the key is valid *and* entitled for truck routing. A `401` means the key is wrong. A `403` means the key is right and the entitlement is missing. Learn to tell these apart on day one. ## Best practices * **Verify each API you plan to use, individually, before sprint planning.** One `curl` per endpoint. Ten minutes. It will save you a quarter. * **Use separate keys per environment.** Development, staging, production. Never one key across all three — you lose the ability to revoke without an outage. * **Store keys in a secret manager.** Not `.env` committed to git. Not a Slack message. Not a Postman collection you share with a contractor. * **Restrict keys where the platform allows it.** Domain restrictions for browser-exposed keys, IP allowlists for server keys. * **Rotate on personnel change.** Anyone who has read your production key still has it after they leave. * **Log the HTTP status, not just the body.** `403` and `429` mean entirely different things and require different responses. See [Authentication](/getting-started/authentication). ## Common mistakes **Shipping the API key to the browser.** Any key in frontend JavaScript is public. If it has no domain restriction, it is now someone else's key. Proxy through your backend, or use a restricted key you're willing to have scraped. **Treating `403` as `401`.** Teams add retry logic around `403` and generate support tickets. `403` will never succeed on retry — it is a missing entitlement, not a transient failure. **Building on free-tier limits.** Free bundles are a marketing boundary, not an architectural guarantee. If you exceed them in staging, you will exceed them in production. **Requesting a key before knowing the use case.** "We need HERE" is not scopeable. Which API, what volume, what vehicle class, what geography. Without those, whoever issues the key is guessing at your entitlements. **Assuming one key covers web and mobile.** Web tile API access and mobile SDK access are distinct entitlements. Both are available under a single Placematic contract — but they must be enabled explicitly. **Committing the Postman collection to a public repo.** It contains the key. This happens more than anyone admits. ## FAQ **How long does it take to get a HERE API key?** Self-serve on HERE's portal: minutes. Through Placematic: one business day, with entitlements confirmed for your use case. **Do I need a credit card?** Not through Placematic. No credit card for pilot access, and no automatic conversion to a paid plan. **Do I need a demo call first?** No. You can request keys without one. **Do I need my own HERE account?** No. Placematic USA LLC is the contracting and billing party. You get one invoice from Placematic. HERE does not invoice you. **Can I test truck routing on the free tier?** Confirm this against your specific account before planning around it. Free-tier product inclusion varies and should not be assumed from general documentation. **Is there a minimum volume commitment?** Not for pilot access. Production contracts define volume tiers based on your expected usage, agreed jointly — not imposed as an arbitrary floor. **Can I use my existing HERE key with Placematic?** If you currently license HERE directly, migration to a Gold Partner contract is possible and frequently improves pricing. Bring your current terms and we'll tell you honestly whether we can beat them. **What's the difference between an API key and an OAuth token?** Different auth mechanisms for different APIs and different security postures. Covered in [Authentication](/getting-started/authentication). ## Related guides API keys, OAuth, key rotation, and what each HTTP error code actually means. What happens when the free tier ends. Decide which entitlements to request before you request them. The platform overview, if you skipped it. Also relevant: [Truck Routing Guide](/guides/truck-routing) · [Migrating from Google Maps](/guides/google-migration) *** Need production HERE API keys or enterprise licensing? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate costs, migrate from Google Maps and build production-ready geospatial solutions. # HERE Pricing Explained Source: https://docs.placematic.com/getting-started/here-pricing-explained How HERE API pricing actually works — call volume vs asset-based models, where costs diverge from Google Maps, and how to model your real bill before you commit. HERE pricing is usage-based, product-specific, and dependent on three variables: which APIs you call, how many times, and whether you buy direct or through a partner. That third variable moves the number more than most teams expect. There is no flat monthly fee. There is no single price list that answers "what will HERE cost us." Anyone who gives you a number without asking about your call mix is guessing. ## Why this matters Most cost comparisons between mapping platforms are done wrong. A team pulls up two per-1,000-request rates, sees they're close, and concludes the platforms are priced similarly. At entry level, they are. HERE Routing via Placematic starts at **\$2.92 per 1,000 requests** above the free tier. TomTom lists €2.50. Google is in the same neighbourhood. If you stop there, you learn nothing. The divergence appears in four places: * **Volume tiers.** Rates drop meaningfully as volume rises. Routing sees the steepest curve. * **Matrix operations.** One matrix call versus N×M sequential routing calls is a different order of magnitude, not a different rate. * **Batch endpoints.** Asynchronous geocoding is materially cheaper per record than the real-time endpoint. Most teams pay real-time rates for overnight work. * **Free bundles.** Each API has its own. They don't pool. At production volume, teams migrating from Google Maps typically see **40–70% savings**. Geocoding-heavy workloads see **80%+**. Those are the honest ranges — not a headline number. Do not model your HERE bill from your Google bill. The meters do not map one to one. Google charges per session in places where HERE charges per request, and vice versa. Instrument your actual per-endpoint call counts first. ## What are the two pricing models? Priced per API call. Costs scale with usage. Best for SaaS platforms embedding HERE APIs into a product, where call volume tracks customer count. Priced per tracked asset per month. Costs scale with fleet size, not API chatter. Best for fleet operators tracking a known number of vehicles. The choice is not cosmetic. A dispatcher who reroutes a 200-truck fleet forty times a day generates enormous call volume against a stable asset count. Under call-volume pricing that team pays for operational diligence. Under asset-based pricing they don't. Inversely, a SaaS platform with 10,000 end customers making one geocode each does not want to be billed per "asset." **Rule of thumb:** if you can count your vehicles and can't predict your calls, go asset-based. If you can predict your calls and don't own vehicles, go call volume. Asset-based pricing availability depends on your contract tier. If your model depends on it, confirm availability for your specific tier before you build a business case on it. ## Base Plan vs Enterprise Plan **Base Plan** — transaction-based billing with free monthly bundles. Suitable for teams starting out or with genuinely variable usage. You pay only above the bundle. **Enterprise Plan** — cost certainty through volume-based and asset-based pricing. Includes fleet optimization SDK, HERE Navigate SDK, and offline maps. Suitable for commercial fleet operators and large-scale deployments. The jump between them is not primarily about price per call. It is about what you're entitled to. Offline maps and the Navigate SDK are not purchasable as Base Plan add-ons. ## When should you buy through a reseller? The honest answer: it depends on your volume and your tolerance for procurement. **Buy direct if** you are a large enterprise with a dedicated vendor management function, you can meet minimum volume commitments comfortably, and you want the vendor relationship in-house. **Buy through a Gold Partner if** any of the following apply: * You want partner pricing tiers, which are not available to direct customers at standard volume levels * You don't want to negotiate minimum volume commitments * You want one contract, one invoice, and one accountable party for the integration working — not just for the API being up * You want the entitlement and licensing questions resolved before your sprint starts * You want implementation support included rather than billed separately Placematic USA LLC is the contracting and billing party. You never receive a HERE invoice. You never negotiate with HERE. If you are currently licensing HERE directly and want to compare — bring your current pricing. We will tell you honestly whether we can improve it. Sometimes the answer is no. ## Key concepts **Every API is a separate meter.** Routing, geocoding, tiles, matrix, tour planning. No bundling. Forecast each independently. **Free bundles are per-API and do not pool.** Exhausting your routing bundle does not consume your geocoding bundle, and unused geocoding does not subsidize routing. **Tiles are billed per request or per session, depending on implementation.** Web SDK, mobile SDK, and static maps bill differently for what looks like the same map. Know which one you shipped. **Fleet Optimization and Tour Planning are enterprise-tier, typically on a committed plan.** They are purpose-built for multi-stop optimization. Approximating them with routing calls costs more and produces worse routes. **Contract length affects rate.** Longer commitments buy lower per-call pricing. Through a partner, contracted pricing does not reprice mid-term. ## How do I estimate my costs? Model it in this order. Skipping steps produces a number you cannot defend. 1. **Instrument.** Count actual calls per endpoint per month in your current system. Not estimates. Logs. 2. **Classify by latency tolerance.** Which calls must be real-time? Which could run as an overnight batch? This alone often moves the bill 30%+. 3. **Identify cacheable calls.** Geocoding the same customer address on every order is pure waste. Addresses are stable. 4. **Separate matrix work from routing work.** If you compute distances between many origins and destinations, you have a matrix workload, whichever API you currently use. 5. **Model both commercial models.** Run your numbers under call-volume and asset-based. Pick the one that survives your worst month. 6. **Then compare vendors.** Not before. The [cost calculator](https://placematic.com/here-location-services/here-pricing/) gives you a range based on volume, API type, and contract length. It is a range, not a quote. For an exact figure tied to your call mix, ask. ## Best practices * **Cache geocoding results aggressively.** The single highest-leverage cost reduction available. Addresses do not move. * **Batch anything that tolerates latency.** Nightly normalization, historical backfills, bulk imports. * **Set `return` explicitly on routing calls.** Do not fetch full turn-by-turn instructions when you need a duration. * **Alert on per-endpoint call rate, not just total spend.** A runaway retry loop is visible in call rate hours before it's visible on an invoice. * **Re-forecast quarterly.** Usage patterns drift. So do your customers. * **Ask what happens at 3× volume before you sign.** Growth should reduce your unit cost, not surprise you. ## Common mistakes **Comparing list prices instead of workload cost.** Entry-level rates are close across vendors. Your bill is determined by call mix, tier placement, and whether you batched. **Paying real-time rates for batch work.** Nightly address normalization does not need sub-100ms latency. It needs to be cheap. **Building tour planning on the routing API.** N routing calls to approximate multi-stop optimization is slower, more expensive, and produces inferior routes than the product designed for it. **Ignoring the free bundle in your model, in both directions.** Some teams forget it exists and overestimate. Others assume it scales and underestimate badly. **Assuming asset-based pricing is available.** It depends on tier. Confirm before it becomes load-bearing in a business case presented to your CFO. **Not modelling the migration cost.** Engineering time to migrate is a real line item. If the payback period exceeds eighteen months, the savings argument is weaker than it looks. Say so out loud. ## FAQ **How much does HERE cost?** It depends on which APIs, at what volume, on what contract length, bought through whom. HERE Routing via Placematic starts at \$2.92 per 1,000 requests above the free tier. That is a starting rate, not a forecast. **Is HERE cheaper than Google Maps?** At production volume, usually — typically 40–70% lower, and 80%+ on geocoding-heavy workloads. At low volume, the difference is negligible and not worth a migration. **Is Placematic's pricing better than going to HERE directly?** As a Gold Partner, Placematic accesses pricing tiers not available to direct customers at standard volume levels. You also avoid procurement overhead and minimum volume commitments. Bring your current HERE pricing and we'll tell you honestly whether we can improve it. **Who invoices me?** Placematic USA LLC, exclusively. One invoice covering all HERE services you use. HERE does not invoice you. **Is there a minimum volume commitment?** Not for pilot access. Production contracts define volume tiers jointly based on expected usage. **Does the pilot auto-convert to a paid contract?** No. If HERE fits, we scope production together. If it doesn't, we tell you. **What is a "transaction"?** One billable API call. Definitions vary by product — a tile request, a route calculation, a geocode. Confirm the unit per API before forecasting. **How accurate is the cost calculator?** It produces a range using Gold Partner pricing as a baseline. It is a planning tool, not a quote. ## Related guides Cost forecasting starts with knowing which APIs you actually need. Where the cost gap comes from, and where it doesn't exist. Free tier boundaries and entitlement confirmation. Modelling migration cost against projected savings. Also relevant: [Truck Routing Guide](/guides/truck-routing) · [Batch Geocoding](/guides/batch-geocoding) *** Model your own bill: [HERE API pricing calculator](https://placematic.com/here-location-services/here-pricing/) Need production HERE API keys or enterprise licensing? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate costs, migrate from Google Maps and build production-ready geospatial solutions. # HERE vs Google Maps – Which Platform Should You Choose? Source: https://docs.placematic.com/getting-started/here-vs-google-maps An honest comparison of HERE Location Services and Google Maps Platform — where each genuinely wins, what migration actually costs, and when you should not switch. Choose Google Maps if your product answers questions about *places*. Choose HERE if your product answers questions about *movement* — particularly the movement of commercial vehicles. Most teams evaluating this comparison are looking at their Google invoice. That's a legitimate trigger. It is not, by itself, a sufficient reason. ## Why this matters Migration is real engineering work. Response schemas differ. Coordinate ordering differs. The tile aesthetic differs, and your designers will file a ticket about it. Frontend code coupled to `google.maps.*` does not port. If your Google bill is \$400/month, the payback period on that work is measured in years. Do something else. If your Google bill is \$40,000/month and half of it is geocoding you should have cached and batched — that's a different conversation, and the savings are typically **40–70%**, reaching **80%+** on geocoding-heavy workloads. Model migration engineering cost against projected savings before you present a business case. A payback period beyond eighteen months means the argument is weaker than the headline number suggests. Say so before someone else does. ## Where does HERE genuinely win? **Commercial vehicle routing depth and coverage.** Truck constraints — height, gross weight, axle count, weight per axle group, trailer count, tunnel category, hazmat class — are first-class routing inputs in 200+ countries. Google added Large Vehicle Routing in August 2026 with dimensions, gross weight, axle count and hazmat classes, for the 48 contiguous US states. What its documentation lists as unsupported: truck toll prices, speed limits, radioactive cargo. There is no offline navigation. The gap is now the constraint tail and the coverage boundary, not the existence of a truck profile. **Large origin-destination matrices.** HERE handles large matrices in a single call. Depot-to-stop optimization across a real fleet is one request, not a nested loop. **Cost at production volume.** The divergence appears above roughly 100k calls/month and widens. Routing sees the steepest curve. Geocoding-heavy workloads see the largest gap. **Contractual price stability.** HERE pricing through a Gold Partner is contractual. It does not reprice mid-term. Teams who lived through Google's 2018 and 2025 pricing changes weight this heavily, and they are right to. **Transport-specific map layers.** Weight restrictions, height limits, hazmat routing attributes, and truck-relevant POIs built into the base data. **EV routing with charge-state awareness.** Range, consumption curve, and charging stops as routing inputs — not as a separate lookup you stitch together. ## Where does Google genuinely win? Any comparison that omits this section is marketing. **Places and POI data.** Google's business database — hours, reviews, photos, categories, closures — is categorically better. This is not close either. **Consumer autocomplete.** For "coffee shop near me," Google's ranking, aliasing, and typo tolerance reflect enormous consumer query volume that HERE does not have. **Street View.** HERE has no equivalent product. **Ecosystem and hiring.** More engineers have shipped Google Maps integrations. More Stack Overflow answers exist. This is a real cost consideration, not a soft one. **Low-volume simplicity.** Under \~10k calls/month, both are effectively free. Google's onboarding is faster. **The best architecture for many products is both.** Google for consumer-facing place search and autocomplete. HERE for routing, matrix operations, and batch geocoding. Nothing requires vendor monogamy, and the split follows the strengths cleanly. ## Side by side | | HERE | Google Maps Platform | | ----------------------------- | ------------------------------------------ | --------------------------------------- | | **Truck routing** | Full profile, 200+ countries | Since Aug 2026, 48 contiguous US states | | **Truck toll cost** | Toll Cost via Routing v8 | Not supported | | **Offline truck navigation** | HERE SDK Navigate Edition | Not available | | **Large matrices** | Single-call, large origin/destination sets | Smaller ceilings; more calls | | **POI / business data** | Adequate | Best in class | | **Consumer autocomplete** | Adequate for addresses | Best in class | | **Street View** | None | Yes | | **Cost at low volume** | Comparable | Comparable | | **Cost at production volume** | Typically 40–70% lower | Baseline | | **Price stability** | Contractual via partner | Has repriced historically | | **Ecosystem familiarity** | Narrower | Very broad | | **EV routing** | Charge-state aware | Limited | | **GPS trace matching** | Route Matching API | No equivalent | ## When should you migrate? Migrate if two or more of these are true: * You route commercial vehicles and are currently using car routing * Your monthly spend exceeds five figures and is dominated by routing or geocoding * You compute large distance matrices with sequential calls * Price volatility from your current vendor is a board-level concern * You need truck-attributed map data, and are currently approximating it Do **not** migrate if: * Your spend is under \~\$1,000/month. Optimize your call patterns instead — cache geocoding, batch what tolerates latency. You will likely halve the bill without changing vendors. * Your product's core value is consumer place discovery * Your team has no engineering capacity this quarter. A half-finished migration is worse than either endpoint. * The only argument is "cheaper." Cheaper on a feature that regresses is not cheaper. ## Key concepts **The meters do not map one to one.** Google bills per session where HERE bills per request, and the reverse. You cannot derive a HERE forecast from a Google invoice. Instrument per-endpoint call counts first. **Coordinate order and response schemas differ.** Budget for it. It's mechanical work, not hard work, but it isn't zero. **Migrate incrementally.** Geocoding first — it's the smallest surface, and usually the largest saving. Then routing. Never both at once. **Half your savings may not require migration at all.** Caching geocoding results and moving batch work off real-time endpoints reduces spend on *any* platform. Do this before you migrate; it also gives you a clean baseline to measure against. ## Best practices * **Instrument before you evaluate.** Per-endpoint call counts, per month, from logs. Not estimates. * **Optimize on Google first.** Cache, batch, drop unnecessary `return` fields. Then re-measure. The remaining bill is your real migration case. * **Run a scoped pilot on the single highest-cost endpoint.** Prove the savings and the quality on one surface before committing the roadmap. * **Validate quality, not just cost.** Route the same 500 real trips through both platforms. Compare against ground truth, not against each other. * **Test truck routing against trap geometry.** 11foot8 bridge (Durham NC), Storrow Drive (Boston), Southern State Parkway (Long Island). Any platform that routes a 4.1m vehicle through these fails. * **Keep both live during cutover.** Feature-flag by endpoint. Shadow-write. Compare. * **Be explicit about where you're keeping Google.** A hybrid architecture is a decision, not an admission. ## Common mistakes **Comparing per-1,000 list prices.** Entry-level rates are close. Your bill is determined by call mix, volume tier, batching, and free bundles. Comparing rate cards teaches you nothing. **Migrating everything simultaneously.** Two independent systems change, one incident occurs, root cause is ambiguous. Migrate one surface, stabilize, proceed. **Migrating before optimizing.** You may be migrating waste. Cache and batch first, then measure what remains. **Underestimating frontend coupling.** `google.maps.*` in your React components is not a config change. **Assuming HERE POI data will satisfy a consumer search feature.** It will not. Keep Google for that surface, and say so up front. **Presenting savings without migration cost.** Your CFO will ask. Answer before they do. **Treating tile styling as cosmetic.** It is, until the product review. Show the maps to design early. ## FAQ **Is HERE cheaper than Google Maps?** At production volume, typically 40–70% lower, and 80%+ on geocoding-heavy workloads. At low volume, the difference is negligible. **Is HERE data as good as Google's?** For road networks, truck attributes, and traffic — yes, and better on commercial vehicle attribution. For business POIs, reviews, and photos — no. **Can I use HERE tiles with my existing frontend?** If you use Leaflet, OpenLayers, or MapLibre GL, largely a tile URL swap. If you're on the Google Maps JS SDK, it's a rewrite of that layer. **How long does a migration take?** Geocoding alone: often a sprint. Routing with truck constraints and quality validation: meaningfully longer. Full migration including frontend: scope it properly. **Should I migrate everything?** Usually not. Routing and batch geocoding to HERE, consumer place search stays on Google, is a common and sensible endpoint. **What about TomTom, Mapbox, or OSRM?** TomTom is competitive in automotive and European markets, with narrower truck constraints and smaller matrix ceilings. Mapbox is strong on styling, weak on commercial vehicle depth. OSRM is free and yours to operate — which means truck attributes, traffic, and map freshness are also yours to operate. **Does Google offer truck routing?** Yes, since August 2026 — Large Vehicle Routing, in the 48 contiguous United States, with dimensions, gross weight, axle count and hazmat classes. Its documentation lists truck toll prices, speed limits and radioactive cargo as unsupported, and there is no offline navigation. Three questions decide it: do your lanes leave the contiguous 48, do you need toll cost before dispatch, does your driver app work offline. A yes to any one of them settles it. **Will HERE reprice on me?** Contracted pricing through a Gold Partner does not reprice mid-term. ## Related guides Endpoint mapping, sequencing, and how to validate quality during cutover. Model your bill properly before you build the business case. Constraints, units, and the trap geometry that proves they apply. Migrate to the correct API, not the closest-looking one. Also relevant: [What is HERE Location Services?](/getting-started/what-is-here-location-services) · [Getting a HERE API Key](/getting-started/getting-a-here-api-key) *** Need production HERE API keys or enterprise licensing? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate costs, migrate from Google Maps and build production-ready geospatial solutions. # What is HERE Location Services? Source: https://docs.placematic.com/getting-started/what-is-here-location-services A practical explanation of HERE Location Services for engineers — what the platform actually contains, where it beats Google Maps, and when it's the wrong choice. HERE Location Services is a set of REST APIs and SDKs for geocoding, routing, map rendering, positioning, and fleet optimization. It is not a single API. It is a platform of roughly two dozen services, priced separately, with meaningfully different maturity levels. Most engineering teams arrive at HERE for one of two reasons: they need truck routing that respects physical vehicle constraints, or their Google Maps bill grew faster than their revenue. Both are good reasons. Neither is a reason to migrate everything. ## Why this matters The dominant assumption in most engineering orgs is that "maps API" is a commodity — swap the vendor, keep the code. That assumption holds for a static map on a marketing page. It falls apart the moment your product makes decisions based on the response. A routing API that returns a path a 53-foot trailer cannot physically drive is not a cheaper alternative. It is a liability that surfaces as a bridge strike, a rejected delivery, or a driver stuck reversing down a residential street. HERE was built for the case where the geometry of the vehicle matters. That is its actual differentiator — not price, not coverage, not tile quality. Price is the reason teams *start* evaluating HERE. Routing correctness is the reason they *stay*. If you evaluate on price alone, you will pick the wrong APIs. ## What is actually in the platform? Grouped by what you'd actually build with them: Car, truck, pedestrian, EV, and bicycle routing. Truck mode accepts height, weight, axle count, and hazmat class as first-class constraints. Forward geocoding, reverse geocoding, batch geocoding, autocomplete, and POI discovery. Vector and raster tiles, satellite imagery, traffic overlays, and transport-specific layers (restrictions, weight limits). Distance Matrix, Tour Planning, Route Matching, Waypoint Sequence. This is the enterprise tier and it is priced accordingly. There is also Positioning (hybrid GPS/Wi-Fi/cellular), Traffic Analytics, and a family of mobile SDKs. Positioning matters if you track assets indoors or in urban canyons. Most teams never touch it. ## When should you use HERE? Use HERE when at least one of these is true: * **You route commercial vehicles beyond the contiguous United States, under axle-group weight regulation, or with toll cost in the dispatch decision.** Trucks, vans over 3.5t, hazmat, oversized loads. Google added a truck profile for the contiguous 48 in August 2026; the constraint tail and the coverage boundary are where the platforms still diverge. * **You call geocoding or routing at production volume.** The unit economics diverge sharply from Google above roughly 100k calls/month. Geocoding-heavy workloads see the largest gap. * **You need large origin-destination matrices.** Depot-to-stop optimization across a real fleet, computed in one call rather than N×M sequential requests. * **You need contractual price stability.** HERE contracts through a partner do not reprice mid-term. * **You need EV routing with charge-state awareness.** Range, consumption curve, and charging stops as routing inputs. ## When should you *not* use HERE? This is the part most vendor pages omit. **Do not migrate to HERE for these use cases.** You will spend engineering time to arrive somewhere worse. * **Consumer place discovery.** Google's POI database, reviews, photos, and business hours are categorically better. If your product answers "what's a good restaurant near me," stay on Google. * **Very low volume.** Under \~10k calls/month you are optimizing a rounding error. Both platforms have free tiers. Spend the time elsewhere. * **Street View or equivalent.** HERE has no comparable product. * **You need the exact Google Maps look.** Tile styling differs. Your designers will notice. * **Your entire frontend is coupled to `google.maps.*`.** The migration is real work. Do it because routing correctness or cost demand it, not because a slide deck did. A common and reasonable outcome: keep Google for consumer-facing search and autocomplete, move routing and batch geocoding to HERE. Nothing requires you to choose one vendor for everything. ## Key concepts **Transport mode is not a parameter — it is a different routing engine.** `transportMode=truck` changes which road segments are traversable, which turns are legal, and how time is estimated. It is not car routing with a slower speed profile. **Truck constraints are physical, not advisory.** Height in centimetres, gross weight in kilograms, axle count, trailer count, tunnel category, hazmat class. Omit them and you get a route that assumes an unconstrained vehicle. HERE will not warn you. ```bash Truck routing — constrained theme={null} curl "https://router.hereapi.com/v8/routes" \ -H "apiKey: $PLACEMATIC_KEY" \ -G \ --data-urlencode "transportMode=truck" \ --data-urlencode "origin=42.0641,-88.0509" \ --data-urlencode "destination=41.5250,-88.0817" \ --data-urlencode "vehicle[height]=410" \ --data-urlencode "vehicle[grossWeight]=35000" \ --data-urlencode "vehicle[axleCount]=5" \ --data-urlencode "return=summary,polyline" ``` ```bash Same request — constraints omitted theme={null} # Returns a route. Possibly under a 3.9m bridge. # HERE assumes an unconstrained vehicle. No error, no warning. curl "https://router.hereapi.com/v8/routes" \ -H "apiKey: $PLACEMATIC_KEY" \ -G \ --data-urlencode "transportMode=truck" \ --data-urlencode "origin=42.0641,-88.0509" \ --data-urlencode "destination=41.5250,-88.0817" \ --data-urlencode "return=summary" ``` **Every API is billed independently.** There is no bundle. A route calculation, a geocode, and a tile request are three different meters. Cost forecasting means forecasting each call type separately — see [HERE Pricing Explained](/getting-started/here-pricing-explained). **Batch endpoints are not the same product as their real-time counterparts.** Batch geocoding is asynchronous, cheaper per record, and has different SLAs. Teams routinely pay real-time rates for work that should have been batched overnight. **Two commercial models exist.** Call-volume pricing (per API call) and asset-based pricing (per tracked vehicle per month). Fleet operators with predictable vehicle counts and unpredictable call volumes usually want asset-based. SaaS platforms embedding APIs usually want call volume. ## Best practices * **Prototype against the API you will ship.** Do not validate on car routing and deploy truck routing. Different engine, different results, different failure modes. * **Test on known trap geometry.** Route a 4.1m vehicle under the 11foot8 bridge in Durham, NC. Route a truck onto Storrow Drive in Boston. Route a commercial vehicle onto Long Island's Southern State Parkway. If your integration returns any of these paths, your constraints are not being applied. * **Cache geocoding results.** Addresses are stable. Re-geocoding the same customer address on every order is the single most common source of avoidable spend. * **Batch anything that can tolerate latency.** Nightly address normalization, historical backfills, bulk imports. * **Set `return` explicitly.** Requesting full turn-by-turn instructions and polylines when you only need duration inflates response size and processing time. * **Instrument per-endpoint call counts before you migrate.** You cannot forecast a HERE bill from a Google bill. The meters do not map one to one. ## Common mistakes **Assuming truck routing is enabled by setting `transportMode=truck`.** It selects the engine. It does not supply your vehicle's dimensions. Both are required. **Treating the free tier as a production sandbox.** Free tiers change, and not every API is included. Truck routing availability in the free tier specifically should be confirmed for your account before you build a pilot around it. **Migrating geocoding and routing simultaneously.** Two independent systems, two independent failure surfaces, one incident with no clear cause. Migrate one, stabilize, then the next. **Comparing list prices instead of workload cost.** Per-1,000 rates are close between vendors at entry level. The divergence is in matrix operations, batch endpoints, volume tiers, and free bundles. Model your actual call mix. **Ignoring the mobile SDK licensing question until late.** Web tile access and mobile SDK access are separate entitlements. Discovering this during your app store submission is expensive. **Building tour planning on top of the routing API.** Multi-stop optimization is a distinct product with distinct pricing. Approximating it with N routing calls is slower, costs more, and produces worse routes. ## FAQ **Is HERE Location Services the same as HERE Maps?** No. HERE Maps is the consumer product. HERE Location Services is the developer platform. Only the latter is licensable. **Do I need a HERE account to use HERE APIs?** Not if you license through a Gold Partner. Placematic USA LLC is the contracting and billing party. You receive one invoice and never negotiate with HERE directly. **Is HERE data as good as Google's?** For road network geometry, truck attributes, and traffic — yes, and in commercial-vehicle attribution, better. For business POIs, reviews, and photos — no. **How current is HERE map data?** Map data ships on a weekly release cadence. Traffic flow and incident data update in near-real-time from probe and sensor feeds. **Can I use HERE tiles with Leaflet or MapLibre?** Yes. Tiles are served over standard HTTP. If you already use Leaflet, OpenLayers, or MapLibre GL, integration is largely a tile URL swap. **Should I buy directly from HERE or through a reseller?** Depends on volume and procurement tolerance. Direct contracts typically involve minimum volume commitments and a sales cycle. Gold Partner pricing tiers are not available to direct customers at standard volumes. See [HERE Pricing Explained](/getting-started/here-pricing-explained) for the honest version. **How long until I have a working API key?** Through Placematic: one business day, no credit card, no demo call required. ## Related guides Sandbox vs production keys, and what "free tier" actually covers. Call volume vs asset-based pricing, and how to model your real bill. A decision tree by use case, not by product family. Where each platform genuinely wins. Also relevant: [Authentication](/getting-started/authentication) · [Truck Routing Guide](/guides/truck-routing) · [Migrating from Google Maps](/guides/google-migration) *** Need production HERE API keys or enterprise licensing? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate costs, migrate from Google Maps and build production-ready geospatial solutions. # HERE Batch Geocoding — Implementation Guide Source: https://docs.placematic.com/guides/batch-geocoding Implementing the HERE Batch API v7 job lifecycle — create, start, poll, retrieve, delete — plus concurrency limits, the 204 that is not an error, and why batch is the first thing you should migrate. # HERE Batch Geocoding Batch geocoding is not a bulk endpoint. It is a **job API**, with a lifecycle you must manage. Teams reach for it expecting `POST /geocode` with an array. They get a job ID, a status URL, and a set of behaviours that look like errors and are not. ## What problem this guide solves You have 4 million customer addresses to normalize. Nothing is waiting. A user is not watching a spinner. Paying real-time geocoding rates for this is the second-largest avoidable cost on the platform, after not caching at all. Batch is cheaper per record, and the only thing you give up is latency you were not using. **Batch geocoding is the correct first surface to migrate off Google.** Smallest blast radius, largest saving, no user-facing risk. If a nightly job breaks, you fix a nightly job. See [Migrating from Google Maps](/guides/google-migration). ## When to use Batch API * Bulk address normalization on import * Backfilling coordinates for an existing customer or location table * Nightly enrichment pipelines * Processing partner data files * Historical telematics enrichment via [batch reverse geocoding](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/reverse-geocoding.html) * Any workload where the result is written to a database rather than rendered to a screen **The tell:** if nothing is waiting for the answer, it should have been batched. ## When NOT to use it * **A user is waiting.** Checkout address entry, dispatcher lookup. Use real-time [geocoding](/guides/geocoding). * **The address is already in your cache.** Batch is cheap. Cached is free. * **Fewer than a few hundred records.** Job overhead exceeds the saving. * **You need results within seconds.** Jobs are asynchronous by design and by nature. * **You want a bulk endpoint.** This is not that. It is a job lifecycle. ## The job lifecycle Six steps. Skipping any of them produces the failure modes below. 1. **Create** — `POST` the job → `201` with a job ID 2. **Start** — begin processing 3. **Poll status** — until the job reaches a terminal state 4. **Retrieve results** — as a downloadable stream 5. **Retrieve errors** — separately, if any 6. **Delete** — clean up HERE documents [job status](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/job-status.html), [job lifecycle](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/job-lifecycle.html), and [limits and performance](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/limits-and-performance.html) as three separate concerns. Read all three before you write the pipeline. The limits page in particular will determine your architecture. ## Status codes that are not errors This section exists because these three produce more support tickets than everything else combined. | Response | Meaning | Do | | ------------------------ | ---------------------------------- | ------------------------------------------------------ | | `429` on job start | Concurrent job limit exceeded | Queue. Back off. This is a quota, not a fault. | | `204` on errors endpoint | Job completed with **zero** errors | Celebrate. Do not retry. | | `404` on results | Job has not succeeded yet | Poll status first. Results do not exist until success. | A `204` from the errors endpoint means there were no errors. Code that treats any non-`200` as failure will log a successful job as broken, every single night, until someone reads the spec. ## Code examples Batch API v7 request formats, the base host, and the exact endpoint paths for create/start/status/result are versioned and entitlement-dependent. We do not publish a host we have not verified against your account. Start from HERE's own quick start, which is maintained and correct: * [Batch API v7 quick start](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/batch-api-quick-start.html) * [Job lifecycle](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/job-lifecycle.html) * [Limits and performance](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/limits-and-performance.html) Authentication follows [Authentication](/getting-started/authentication). The pipeline logic below is what matters, and it is platform-independent. ## Production architecture **Persist the job ID before you poll.** If your worker restarts mid-poll, you resume. You do not resubmit. Resubmitting a 4-million-record job bills 4 million records again. **Poll with backoff.** A job that takes forty minutes does not need to be checked every two seconds. **Respect the concurrency limit.** Queue jobs. A `429` on start means you already have as many running as your entitlement allows. Firing retries in a loop achieves nothing. **Use gzip.** Compression is supported and worth it on large inputs. Upload time and bandwidth both drop materially. **Handle partial success.** Some records fail. The errors endpoint tells you which. A pipeline that assumes all-or-nothing will either discard 3.9 million good results or persist 100,000 bad ones. **Delete completed jobs.** They accumulate. Cleanup is step six for a reason. **Webhooks exist in beta.** HERE supports completion notifications with substitutable placeholders like `${JOB_ID}` and `${JOB_STATUS}`. Attractive — but beta. If your nightly pipeline's correctness depends on a webhook arriving, build the polling fallback anyway. **Normalize before you submit.** Trim, case-fold, and expand abbreviations first. It increases your cache hit rate on the next run and reduces records that fail to match. **Write results into your cache, not just your table.** The point of batch geocoding is that you never geocode that address again. See [Geocoding](/guides/geocoding). The correct end state is a table where every address has coordinates, a normalized form, a confidence score, and a `geocoded_at` timestamp. New addresses arrive at a trickle and are geocoded in real time. The batch job runs once, at migration, and then rarely. ## Cost and usage considerations Batch is cheaper per record than real-time. That is the entire commercial case, and it is sufficient. Where teams overspend: * **Never batching at all.** Real-time rates for overnight work. * **Re-batching unchanged records.** Filter on `geocoded_at IS NULL` before you build the input file. * **Resubmitting jobs after a worker crash.** Persist the job ID. * **No caching downstream.** You paid once. Do not pay again next month. * **Batching what should have been cached.** If your input file contains the same address 400 times, deduplicate it before submission. Deduplicate your input file. A raw export of order addresses contains enormous repetition. Geocoding 4 million rows that contain 900,000 distinct addresses bills for 4 million. Geocoding-heavy workloads see the largest savings when moving from Google — typically 80%+ at production volume. That figure assumes the pipeline is already sane. Migrating an unbatched, undeduplicated pipeline moves waste to a cheaper meter. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Common mistakes **Treating Batch API as a bulk endpoint.** It is a job lifecycle. Poll it. **Retrying `204` from the errors endpoint.** Zero errors. **Retrying `404` on results aggressively.** The job has not finished. **Hammering after a `429` on start.** Concurrency limit. Queue. **Losing the job ID on restart.** Full rebill. **Assuming all-or-nothing.** Read the errors endpoint. **Never deleting jobs.** **Depending on a beta webhook** without a polling fallback. **Not deduplicating the input file.** **Not writing results back into a persistent cache.** You will do this again next quarter, and pay again. ## Best practices * Deduplicate and normalize the input before submission * Persist the job ID before the first poll * Poll with exponential backoff * Queue against the concurrency limit rather than retrying `429` * Compress large inputs with gzip * Read the errors endpoint; handle partial success explicitly * Delete jobs after retrieval * Write results into a durable cache keyed on normalized address * Build the polling path even if you use webhooks * Filter on already-geocoded records before every subsequent run ## API reference * [Batch API v7 quick start](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/batch-api-quick-start.html) * [Job lifecycle](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/job-lifecycle.html) * [Job status](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/job-status.html) * [Limits and performance](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/limits-and-performance.html) * [Batch reverse geocoding](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/reverse-geocoding.html) ## Related guides Real-time endpoints, caching strategy, and when latency is worth paying for. Batch reverse geocoding for historical telematics enrichment. Batch is the first surface to move. Smallest risk, largest saving. Batch versus real-time is the largest per-record cost lever. Also: [Authentication](/getting-started/authentication) · [Batch Geocoding](https://placematic.com/here-location-services/batch-geocoding/) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial solutions. [Talk to us](https://placematic.com/contact/). # HERE Catchment Area and Isolines — Implementation Guide Source: https://docs.placematic.com/guides/catchment-area Computing travel-time service areas with HERE — isolines versus radius buffers, range types, when a polygon lies to you, and the site-selection workflow that depends on it. # HERE Catchment Area and Isolines A catchment area answers: everywhere reachable from this point within N minutes. The naive version is a circle. The circle is wrong, and it is wrong in a way that produces confident, defensible, incorrect business decisions — because a river, a highway with no exit, or a rail line does not care about Euclidean distance. ## What problem this guide solves Site selection. Delivery zone definition. Service territory design. "Which stores can reach this customer in 30 minutes." "How many households are within a 15-minute drive." Every one of these is a reachability question, and every one is routinely answered with a radius because a radius is easy. A 10-mile radius around a store in Chicago includes Lake Michigan. A 20-minute isoline does not. This is not a rounding error. It is a category error, and it shows up in expansion decisions that cost real money. ## When to use isolines * Trade area and catchment modelling for retail site selection * Delivery zone boundaries priced by drive time * Service territory design for field operations * "Is this address serviceable" containment checks * Coverage-gap analysis across a location network * Response-time modelling for dispatch ## When NOT to use it * **Point-to-point travel time.** Use [Routing](/guides/routing). An isoline tells you the boundary, not the path. * **Many-to-many travel times.** Use [Matrix Routing](/guides/matrix-routing). If you want "travel time from 200 stores to 3,000 customers," that is a matrix, not 200 isolines. * **Real-time containment checks on every request.** Compute the polygon once, store it, query it in your own database. See below. * **When a radius genuinely suffices.** Dense urban grid, short distances, low stakes. Be honest. A 5-minute walking isoline in Manhattan is close to a circle, and the isoline call costs money. ## Key concepts **Isolines come in range types.** Time, distance, and consumption. Time is what most business questions actually mean, and distance is what most teams accidentally request. **Transport mode changes the shape dramatically.** A truck isoline is smaller and differently shaped than a car isoline from the same origin, because trucks cannot use every road. If you are modelling truck service areas, use truck mode with [vehicle constraints](/guides/truck-routing). **Departure time changes the shape.** A 30-minute isoline at 3am and at 5pm are different polygons. If you compute at an arbitrary hour and apply the result to peak operations, your delivery zone is a fiction. **Isolines are approximations with a resolution parameter.** The returned polygon is a simplification of a reachability set. Higher resolution means a more accurate boundary and a larger response. Do not treat the vertices as ground truth for individual addresses near the edge. **Origin versus destination matters.** Reachability *from* a point and reachability *to* a point are different polygons where one-way streets, ramps, and turn restrictions exist. Ask the question you actually mean. "Which customers can we reach in 30 minutes" is a *from-origin* isoline. "Which customers can reach us in 30 minutes" is a *to-destination* isoline. They are not the same polygon, and the difference matters most exactly where it is hardest to notice. ## Code examples HERE's isoline capability is delivered through the Routing product family, and the endpoint, range specification, and resolution parameters are versioned. Rather than publish a request we cannot verify against your entitlement and API version, we recommend starting from HERE's maintained reference: * [HERE Routing documentation](https://www.here.com/docs/category/routing-api-v8) — isoline routing * [Routing API v8 reference](https://docs.here.com/routing/reference/routing-api-v8-calculateroutes) Placematic provides a working request scoped to your account and use case during a pilot. The authentication pattern is identical to [Routing](/guides/routing). The response is a polygon. Everything below concerns what you do with it. ## Production architecture **Compute once, store, query locally.** This is the entire architectural insight. An isoline for a fixed store location, a fixed range, and a representative departure time is stable for months. Compute it. Store the polygon in PostGIS, or any spatial store. Answer "is this address in our delivery zone" with a `ST_Contains` query costing nothing. If your system calls HERE's isoline endpoint on every serviceability check at checkout, you have built a spatial database with an API bill attached. Materialize the polygons. **Recompute on a schedule, not on demand.** Road networks change slowly. Quarterly is usually generous. Recompute when you open a location, close one, or change your service promise. **Store multiple time bands.** 10, 20, 30 minutes as nested polygons. Delivery pricing by band becomes a containment test, not three API calls. **Model peak and off-peak separately** if your operation spans both. Two polygon sets, chosen by time of day. **Simplify polygons before storing** if you do not need vertex-level precision. Smaller geometry, faster containment queries, and the isoline was an approximation to begin with. **Beware overlapping catchments.** Two stores' 20-minute polygons will overlap. Assigning a customer to "the store whose catchment contains them" is ambiguous by construction. Assign by actual travel time from [Matrix Routing](/guides/matrix-routing), and use catchments for coverage visualization. ## Cost and usage considerations Each isoline computation is a billable transaction, and isolines are computationally heavier than point-to-point routes. Where teams overspend: * **Calling isolines for containment checks.** The dominant waste. Materialize the polygon; query it locally. * **Computing high-resolution polygons for coverage maps** where a coarse boundary renders identically at the zoom level shown. * **Recomputing unchanged catchments.** Store locations do not move. * **One isoline per customer query** where one isoline per store would have served every customer. The right pattern turns an unbounded per-request cost into a bounded per-location cost. For a 400-store network with three time bands, that is 1,200 calls per quarter, not per day. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Common mistakes **Using a radius buffer instead of a drive-time isoline.** Rivers, lakes, and limited-access highways. **Calling the API on every serviceability check.** Materialize. **Ignoring departure time.** A midnight polygon applied to rush-hour operations. **Using car mode for a truck service area.** Trucks cannot use every road in the polygon. **Requesting distance range when you meant time range.** Both are valid. Only one answers your question. **Confusing from-origin with to-destination reachability.** **Treating polygon vertices as authoritative for edge addresses.** It is an approximation with a resolution setting. **Assigning customers to stores by catchment membership.** Overlaps make this ambiguous. Use travel time. ## Best practices * Materialize polygons into a spatial database; query containment locally * Recompute on a schedule tied to network and location changes, not on demand * Store nested time bands as separate polygons * Use the transport mode and vehicle constraints you actually operate * Set departure time to a representative hour, and model peak separately if it matters * Choose resolution deliberately; simplify before storage * Use [Matrix Routing](/guides/matrix-routing) for store assignment; use isolines for coverage ## API reference * [HERE Routing API v8](https://www.here.com/docs/category/routing-api-v8) — isoline routing * [Routing API v8 reference](https://docs.here.com/routing/reference/routing-api-v8-calculateroutes) ## Related guides The right tool for assigning customers to locations. The base engine and authentication pattern isolines share. Truck service areas are smaller and differently shaped. Resolving the addresses you will test for containment. Also: [HERE Pricing Explained](/getting-started/here-pricing-explained) · [Catchment Area API](https://placematic.com/here-location-services/catchment-area/) · [UpInsight](https://placematic.com/spatial-data-analytics/) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial solutions. [Talk to us](https://placematic.com/contact/). # HERE EV Routing — Implementation Guide Source: https://docs.placematic.com/guides/ev-routing Implementing electric vehicle routing with HERE — consumption models, charging stops, the separate EV Charge Points API, and when EV routing is not what you need. # HERE EV Routing EV routing is not car routing with a range filter bolted on. It is a routing problem where the vehicle's energy state changes along the path, and where the route may require stopping to acquire more energy. That single difference — that the vehicle's ability to continue depends on decisions made earlier in the route — is why you cannot approximate it by post-processing a car route against a charger database. ## What problem this guide solves You are routing a vehicle that can run out of energy in a way that a diesel truck effectively cannot, because refuelling infrastructure is dense and charging infrastructure is not, and because charging takes an hour rather than eight minutes. The system must answer: can this vehicle complete this route, where must it stop, and how long does the stop take. ## Two distinct HERE products Do not confuse these. They are different APIs with different entitlements. **EV routing** is a capability within the Routing API. It accepts a consumption model and current charge state, and returns a route that may include charging stops. **HERE EV Charge Points API** is a separate product for finding and describing charging infrastructure — station locations, connector types, power feeds, availability, tariffs. It exists at [v2 and v3](https://www.here.com/docs/category/ev-charge-points-api-v3), with a documented migration path between them. If you need a map of chargers, you want the EV Charge Points API. If you need a route that reaches its destination, you want EV routing. Most real products need both, and they bill separately. ## When to use EV routing * Fleet dispatch where range is a binding constraint * Long-haul EV routing across sparse charging corridors * Range-anxiety features in driver-facing applications * Feasibility scoring before assigning an EV to a job * Electrifying an existing diesel route and needing to know whether it survives ## When NOT to use it * **Short urban routes well inside range.** Standard [routing](/guides/routing) is cheaper and simpler. If every job is 40 km and the vehicle has 300 km of range, charging is not a routing problem — it is a depot scheduling problem. * **Displaying nearby chargers.** That is the EV Charge Points API. * **Charging session management or payment.** Neither product does this. * **Modelling total cost of ownership.** Routing gives you energy consumption, not economics. ## Key concepts **A consumption model is a curve, not a constant.** Energy use varies with speed, gradient, and auxiliary load. A model that assumes linear consumption will be optimistic on highways and on hills. **Charge state is an input, not an assumption.** Routing from a vehicle at 22% charge produces a materially different route than the same vehicle at 90%. If your telematics feed provides live state of charge, use it. If it does not, be honest with the driver about what the ETA assumes. **Charging stops are routing outputs.** The engine may insert them. Your UI and your dispatch logic must handle a route with stops that the dispatcher did not request. **Charger availability is a separate query and a separate freshness problem.** A route computed against static charger data will confidently send a truck to an occupied or out-of-service charger. If availability matters, query it — and know that availability data has a latency that route computation does not. **Truck charging is not car charging.** HERE documents EV truck charging locations as a distinct concern within the EV Charge Points product. Connector types, power levels, and physical site access differ. A route computed against passenger-vehicle chargers is wrong for a Class 8 vehicle. ## Code examples EV routing parameters — consumption model, charge state, charging station preferences — are numerous, versioned, and mode-dependent. Rather than publish an example we cannot fully verify against your entitlement and API version, we link to HERE's maintained reference and recommend confirming the current parameter set before implementation. * [Routing API v8 reference](https://docs.here.com/routing/reference/routing-api-v8-calculateroutes) — EV routing parameters * [EV routing tutorials](https://www.here.com/docs/category/ev-charge-points-api-v3) — HERE's own worked examples * [EV Charge Points API v3 migration guide](https://www.here.com/docs/category/ev-charge-points-api-v3) — if you are on v2 Placematic will scope a working request against your actual vehicle profile and entitlement during a pilot. That is faster and safer than adapting a generic example. The base routing endpoint and authentication are the same as standard routing — see [Routing](/guides/routing) for the verified request shape and [Authentication](/getting-started/authentication) for key handling. ## Production architecture **Vehicle energy profiles are model data.** Battery capacity, consumption curve, connector type, and maximum charge rate belong on the vehicle record, not in the call site. This is the same argument as truck dimensions in [Truck Routing](/guides/truck-routing), and it fails the same way when ignored. **Separate feasibility from dispatch.** Ask "can this vehicle do this job" as a cheap pre-check before you compute the full route with charging stops. Most rejections are obvious. **Cache the network, not the availability.** Charger locations change slowly. Availability changes by the minute. Different TTLs, different storage. **Model the charging stop as a first-class event.** Drivers need to know a stop is coming. Dispatchers need to know a vehicle is unavailable for 45 minutes. A route object that hides the stop inside a polyline will not survive contact with operations. **Degrade honestly.** If live charge state is unavailable, say the ETA assumes a full battery. Do not silently substitute an assumption. ## Cost and usage considerations EV routing bills as a routing transaction. The EV Charge Points API bills separately, under its own entitlement. Where teams overspend: * Querying charger availability on every route computation, when the route was cached * Recomputing full EV routes for feasibility checks that a range comparison would have rejected * Calling the Charge Points API per-map-pan rather than caching the network Confirm entitlement for both products before you architect around them. A key that routes EVs may not query charge points. See [Getting a HERE API Key](/getting-started/getting-a-here-api-key). ## Common mistakes **Approximating EV routing by filtering a car route against a charger table.** The vehicle's ability to reach charger three depends on whether it stopped at charger one. This is not a post-processing problem. **Assuming full charge.** Nearly every real dispatch begins at partial charge. **Ignoring gradient and speed in the consumption model.** Highway and mountain routes break linear assumptions. **Routing a heavy commercial EV against passenger charging infrastructure.** Different connectors, different power, different site access. **Treating charger availability as static.** A confidently routed truck arriving at an occupied charger is worse than no routing at all. **Confusing EV routing with the EV Charge Points API.** Separate products, separate entitlements, separate bills. **Deploying against v2 of the Charge Points API without checking the v3 migration path.** ## Best practices * Store the full energy profile on the vehicle record * Use live state of charge where telematics provides it; disclose the assumption where it does not * Pre-filter infeasible jobs before computing full routes * Cache the charger network aggressively; cache availability barely * Surface charging stops explicitly in dispatch and driver UIs * Verify truck-specific charging data if you route commercial EVs * Confirm entitlement for both routing and charge points before you build ## API reference * [Routing API v8 reference](https://docs.here.com/routing/reference/routing-api-v8-calculateroutes) * [HERE EV Charge Points API v3](https://www.here.com/docs/category/ev-charge-points-api-v3) * [EV Charge Points v2 to v3 migration guide](https://www.here.com/docs/category/ev-charge-points-api-v3) ## Related guides The base engine, request shape, and error semantics EV routing inherits. Physical constraints, which apply to electric trucks as much as diesel ones. Matrix responses can include consumption alongside time and distance. EV routing versus charge points — which question are you asking. Also: [HERE Pricing Explained](/getting-started/here-pricing-explained) · [Getting a HERE API Key](/getting-started/getting-a-here-api-key) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial solutions. [Talk to us](https://placematic.com/contact/). # HERE Fuel Prices API — Implementation Guide Source: https://docs.placematic.com/guides/fuel-prices How to implement HERE Fuel Prices v3 in production — search geometries, fuel type IDs, the US cash-vs-credit trap, and truck accessibility filtering. # HERE Fuel Prices API The Fuel Prices API answers one question: which fuel stations are in this area, and what are they charging right now. It does not tell you what a station costs *your* fleet. Carriers on fuel cards pay negotiated rates, and the pump price is a reference layer, not an invoice. It also does not decide where a truck should stop — that is a routing problem you solve with [Truck Routing](/guides/truck-routing), using this API as one input. ## What problem this guide solves You need priced fuel stations along a corridor or around a point, and you need the ones a tractor-trailer can actually enter. HERE's reference documentation lists every parameter and all 97 fuel type IDs. This guide tells you which of those IDs will silently corrupt a US price comparison, why your corridor request breaks in the browser, and why the station your driver needed was missing from the response. ## When to use HERE Fuel Prices * Showing priced stations around a driver's current position * Finding stations along a planned route, with a corridor width you control * Filtering candidate stops by brand, fuel type, or vehicle accessibility * Building a price-spread view across a lane or region * Feeding station candidates into an optimizer that scores detour cost ## When NOT to use it If you are calling this API to decide *whether* a stop is worth the detour, you are only holding one of the three inputs. Price without detour cost and without vehicle accessibility produces a recommendation the driver cannot follow. * **Calculating what a fleet actually pays.** These are pump prices. Fuel card networks settle at negotiated rates. Use this as a reference layer and model the discount separately. * **Detour cost.** `distance` in the response is air distance. Road distance and drive time come from [Routing](/guides/routing). * **EV charging stops.** Different product entirely. See [EV Routing](/guides/ev-routing). * **General POI discovery.** If you want every amenity at a truck stop, that is [Geocoding & Search](/guides/geocoding), not this. ## Your first request ```bash cURL theme={null} curl -gX GET 'https://fuel.hereapi.com/v3/stations?'\ 'in=circle:41.9921,-88.0035;r=5000&'\ 'fuelTypes=-1&'\ 'sort=price:asc&'\ 'limit=50' \ -H "Authorization: Bearer $HERE_TOKEN" ``` ```python Python theme={null} import os, requests resp = requests.get( "https://fuel.hereapi.com/v3/stations", params={ "in": "circle:41.9921,-88.0035;r=5000", "fuelTypes": -1, # All Truck Diesel "sort": "price:asc", "limit": 50, }, headers={"Authorization": f"Bearer {os.environ['HERE_TOKEN']}"}, timeout=10, ) resp.raise_for_status() data = resp.json() print(data["total"], len(data["stations"])) ``` ```javascript Node.js theme={null} const params = new URLSearchParams({ in: "circle:41.9921,-88.0035;r=5000", fuelTypes: "-1", sort: "price:asc", limit: "50", }); const resp = await fetch( `https://fuel.hereapi.com/v3/stations?${params}`, { headers: { Authorization: `Bearer ${process.env.HERE_TOKEN}` } } ); if (!resp.ok) throw new Error(`HERE returned ${resp.status}`); const { total, stations } = await resp.json(); console.log(total, stations.length); ``` This endpoint documents Bearer token authentication, not the `apiKey` query parameter you may be using on Routing or Geocoding. If you are reusing credential-handling code from another HERE API, check this before you debug anything else. See [Authentication](/getting-started/authentication). ## Key concepts **`in` accepts three geometries, and they are not interchangeable.** A circle (`circle:{lat},{lng};r={meters}`), a bounding box (`bbox:{west},{south},{east},{north}`), or a corridor (`corridor:{flexiblePolyline};r={meters}`). Circle for "near the driver". Bbox for a map viewport. Corridor for a planned route. **Fuel type IDs are the API's real interface.** There are 97 of them plus 8 negative category aggregates. The negative values are what you usually want: `-1` is All Truck Diesel, `-2` All Diesel, `-3` All Petrol. Querying `-1` rather than enumerating `1,11,32,55` saves you from missing a grade a brand happens to report differently. **Accessibility is returned, not filtered.** Each station carries `stationDetails.accessibilities` with values like *Suitable for cars*, *Suitable for medium trucks*, *Suitable for large trucks*. There is no server-side parameter for it. You over-fetch and filter client-side, which has consequences for pagination — see below. **`position` and `access` are different coordinates.** `position` is the site. `access` is where a vehicle enters. On divided highways and frontage roads these diverge enough to change which side of the road a stop is on. Route to `access`. **`sort=distance` is air distance, and only meaningful for circle search.** It ignores the road network entirely. A station 400 metres away across a median with no crossing sorts above one 900 metres ahead on your side. Sort by air distance to shortlist, then score with real routing. **`returnAllStations` defaults to `false`.** Stations with stale or unknown prices are dropped from the response. Your coverage looks worse than it is, and a station the driver can see from the cab is missing from the app. Set it to `true` when the user is choosing a place to stop; leave it `false` when you are computing a price spread. ## The US cash-versus-credit trap This is the failure that will not surface in staging, because it does not exist in Europe. Many US stations post two prices for the same fuel: a cash price and a credit price. HERE models these as **separate fuel type IDs**, not as two fields on one entry. | ID | Name | | -- | ---------------------- | | 1 | Diesel | | 76 | Diesel cash (US ONLY) | | 2 | Regular | | 78 | Regular cash (US ONLY) | | 11 | Truck-Diesel | | 97 | Truck Diesel cash | | 4 | Premium | | 80 | Premium cash (US ONLY) | If you query a category aggregate like `-2` (All Diesel) and then compare the returned prices across stations, you will compare a cash price at one station against a credit price at another and rank them against each other. The spread you surface is fiction, and it is fiction in the direction that makes cash-posting stations look cheapest. Decide which price basis your product uses, then filter the response by fuel type ID before any comparison. Ranking a mixed set of cash and credit prices produces a confidently wrong answer, and nothing in the response flags it for you. For a fleet paying by card, the credit-side IDs are the honest comparison. For a consumer app, cash may be what the user sees on the sign. ## Corridor search: use POST The `corridor:` value in `in` is a Flexible Polyline in a query string. A real interstate leg encodes to a string long enough to exceed URL length limits in browsers and some proxies, and the request dies before it reaches HERE. There is a second endpoint at the same path that takes the corridor in the request body: ```bash cURL theme={null} curl -X POST 'https://fuel.hereapi.com/v3/stations?fuelTypes=-1&limit=50&sort=price:asc' \ -H "Authorization: Bearer $HERE_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "corridor": [ { "lat": 35.9940, "lng": -78.8986 }, { "lat": 36.0999, "lng": -80.2442 }, { "lat": 35.2271, "lng": -80.8431 } ], "width": 5000 }' ``` ```python Python theme={null} import os, requests resp = requests.post( "https://fuel.hereapi.com/v3/stations", params={"fuelTypes": -1, "limit": 50, "sort": "price:asc"}, json={ "corridor": [ {"lat": 35.9940, "lng": -78.8986}, {"lat": 36.0999, "lng": -80.2442}, {"lat": 35.2271, "lng": -80.8431}, ], "width": 5000, }, headers={"Authorization": f"Bearer {os.environ['HERE_TOKEN']}"}, timeout=20, ) resp.raise_for_status() print(resp.json()["total"]) ``` Note the constraint on `width`: **minimum 5000 metres, maximum 20000**, default 5000. You cannot ask for a tight 500-metre corridor. The narrowest search HERE will run is 5 km either side of the line, so every corridor result needs detour filtering on your side regardless of how precise your polyline is. Query parameters stay in the query string on the POST. Only `corridor` and `width` move to the body. ## Truck workflows The sequence that holds up in production: 1. **Query wide, on the right fuel basis.** `fuelTypes=-1` for truck diesel, corridor via POST, `limit` at the ceiling. 2. **Filter on accessibility client-side.** Keep only stations whose `accessibilities` includes *Suitable for large trucks*. Expect this to remove a large share of results — in a dense suburban radius it routinely halves them. 3. **Score the survivors with real routing.** Detour to `access`, not `position`, using [Truck Routing](/guides/truck-routing) with the vehicle profile so height and weight restrictions on the approach are respected. A station rated for large trucks is still unreachable if the only approach runs under a low bridge. 4. **Apply tank state.** How much range remains determines whether a detour is affordable at all, and whether you want a partial fill now and a full fill at a better price downstream. Steps 2 and 3 are where the useful product is. Step 1 is a data call anyone can make. Because accessibility filtering happens after the response, `limit=50` is not 50 usable stations. Paginate with `offset` until you have enough truck-accessible candidates, or set a generous `limit` and accept the payload. Check `HasMore` before assuming you have seen everything. ## Production architecture **Cache stations separately from prices.** The station list for a fixed corridor is stable for weeks. Prices carry a `modified` timestamp and move through the day. One TTL for both means either stale prices or pointless refetching of a station list that has not changed. **Trust the `modified` field, and show it.** Every price entry is individually timestamped. A price from four hours ago and one from four days ago look identical in your UI unless you surface the difference. In Illinois we have seen station records refresh daily with prices updating several times within the same day. **Do not call this on map pan.** A debounced viewport query is fine at human interaction speed. An undebounced one fires a billable request per frame of a drag. **Use the count endpoint for coverage checks.** There is a dedicated stations-count resource. Use it when you want to know whether an area has data at all, rather than pulling 50 full station objects to find out. **Handle `403` as an entitlement failure.** Fuel Prices is licensed separately from Routing and Geocoding. A key that works everywhere else will return `403` here if the product is not on your contract. Retrying will not help. **Set `X-Request-Id`.** The API accepts a caller-supplied trace token. Set it to something you can correlate with your own logs before you need it at 3am. ## Cost and usage considerations Each search is one billable transaction. Fuel Prices meters independently of Routing, Geocoding, and tiles. Where teams overspend: * **Undebounced map interactions.** The single largest avoidable cost in any map-driven fuel UI. * **Refetching an unchanged corridor.** The route did not move. The station list did not move. Only prices did, and a cached station list plus a narrow price refresh is cheaper than a full corridor search. * **Pulling full station objects to test coverage.** Use the count endpoint. * **Over-wide corridors.** A 20 km corridor across a long interstate leg returns a large result set, most of which fails your detour filter anyway. Start at 5 km and widen only where results are thin. See [HERE Pricing Explained](/getting-started/here-pricing-explained) for how transaction and asset-based models differ. ## Common mistakes **Comparing cash prices against credit prices.** Separate fuel type IDs, silently mixed by category queries. Covered above; it is the one to internalise. **Assuming `distance` is drive distance.** It is air distance. Always. **Routing to `position`.** Use `access`. The difference is small in metres and decisive on a divided highway. **Leaving `returnAllStations` at the default in a driver-facing UI.** The station the driver is looking at is missing because its price is stale, and you have taught them the app is unreliable. **Putting a long corridor polyline in a query string.** Works in Postman on a short test leg, fails in the browser on a real one. Use POST. **Filtering accessibility before pagination.** `limit=50` returns 50 stations, not 50 truck-accessible ones. Page until you have candidates, not until you have rows. **Reusing `apiKey` query auth from another HERE API.** This endpoint documents Bearer tokens. **Retrying a `403`.** Patient, well-engineered, permanently futile. ## Best practices * Query `-1` or `-2` category IDs rather than enumerating individual grades * Pick a price basis, cash or credit, and filter to it before any ranking * Use POST for corridors, GET for circles and viewports * Route to `access`, never to `position` * Cache station geometry and price data with different TTLs * Surface the per-price `modified` timestamp in any UI a driver acts on * Debounce every map-driven query * Paginate against truck-accessible candidates, not raw row count * Set `X-Request-Id` on every call and log it beside the status code ## API reference Complete parameter tables, the full 97-entry fuel type list, and response schemas are maintained by HERE: * [Fuel Prices API v3 — stations search](https://docs.here.com/fuel-prices/reference/fuelstationssearch) * [Fuel Prices API v3 — corridor search (POST)](https://docs.here.com/fuel-prices/reference/fuelstationscorridorsearch) * [Fuel Prices API concepts](https://docs.here.com/fuel-prices/docs/concepts) We do not duplicate them. ## Related guides Detour cost to the access point, with the restrictions that decide whether the approach is legal. Turning an air-distance shortlist into real drive time. The same stop-selection problem, with charge state instead of tank level. Where fuel data sits relative to routing, search, and optimisation. Also: [Authentication](/getting-started/authentication) · [HERE Pricing Explained](/getting-started/here-pricing-explained) · [Geocoding](/guides/geocoding) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial solutions. [Talk to us](https://placematic.com/contact/). # HERE Geocoding and Search — Implementation Guide Source: https://docs.placematic.com/guides/geocoding Implementing HERE Geocoding & Search v7 — geocode, autocomplete, autosuggest, discover, batch jobs, and the caching decision that halves most geocoding bills. # HERE Geocoding and Search Half of what teams call a geocoding cost problem is a geocoding *caching* problem. Addresses do not move. Re-geocoding the same customer address on every order is the most common avoidable spend on this entire platform, and it is fixed with a database column. ## What problem this guide solves You have address text. You need coordinates, a normalized address, and a confidence signal. You may also need type-ahead in a UI, or a free-text place search, or to process a million rows overnight. These are four different endpoints with four different cost profiles, and teams routinely use the most expensive one for all four jobs. ## The endpoint family HERE Geocoding & Search v7 exposes distinct endpoints for distinct jobs: | Endpoint | Job | | --------------- | ---------------------------------------------------------------------------------- | | `/geocode` | Known address or place → coordinates. Structured, unstructured, or hybrid queries. | | `/revgeocode` | Coordinates → nearest address. See [Reverse Geocoding](/guides/reverse-geocoding). | | `/autocomplete` | Completes keystrokes to a valid street address or administrative area. | | `/autosuggest` | Suggests address or place candidates from incomplete or misspelled input. | | `/discover` | Free-form text query, results ranked by relevance. | | `/browse` | Places filtered by category or name, ranked by distance from a centre. | | `/lookup` | Retrieve a known place by its HERE ID. | `/autocomplete` and `/autosuggest` are not synonyms. Autocomplete completes *addresses*. Autosuggest handles misspellings and suggests *places*. Picking the wrong one produces a type-ahead that feels broken to users and correct to engineers. Full endpoint list and base URL: [HERE Geocoding and Search API v7 endpoints](https://www.here.com/docs/bundle/geocoding-and-search-api-v7-api-reference/page/index.html). Confirm the base host for your account against HERE's endpoint documentation before hardcoding it. Hosts differ by product and have changed across versions. We deliberately do not reproduce one here rather than risk publishing a stale value. ## When to use each **`/geocode`** — Server-side address resolution. Batch imports where each record is a full address. Anywhere you have a complete address and need a point. **`/autocomplete`** — Address entry fields. Checkout. Dispatcher address input. Cheap, fast, address-scoped. **`/autosuggest`** — Search boxes where users type place names, misspell things, or type partial categories. Handles spelling correction and term completion. **`/discover`** — Free-text search where the user's intent is unstructured. "Coffee near Wrigley Field." **`/browse`** — Category-filtered place lists ranked by distance. Store finders. "Show me pharmacies within 2 km." **Batch API v7** — Any workload where nothing is waiting. See below. ## When NOT to use HERE geocoding Be honest about this before you migrate. For **consumer place discovery** — business names, hours, reviews, photos, categories — Google's data is better. Not marginally. Categorically. If your product answers "what's a good restaurant near me," keep Google for that surface. HERE is strong on **street addresses**. It is adequate on POIs. Choose per use case, not per vendor. A hybrid architecture — HERE for address resolution and routing, Google for place discovery — is a defensible and common outcome. See [HERE vs Google Maps](/comparisons/here-vs-google-maps). Also skip HERE geocoding when: * You are geocoding fewer than \~10k addresses a month. The savings are a rounding error against migration cost. * The address is already geocoded and cached. Which brings us to the main point. ## Real-time versus batch Same data. Different product. Materially different price. **Batch API v7 is a job API**, not a bulk endpoint. The lifecycle is explicit: 1. `POST` to create a job → returns `201` with a job ID 2. Start the job 3. Poll job status 4. Retrieve results as a downloadable stream 5. Retrieve errors separately, if any 6. Delete the job HERE documents [job status](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/job-status.html), [job lifecycle](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/job-lifecycle.html), and [limits and performance](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/limits-and-performance.html) as separate concerns. Read all three before building. Notable behaviours worth knowing before you architect: * Exceeding the concurrent job limit returns `429` on job start * A completed job with no errors returns `204` on the errors endpoint, not an empty `200` * Results are not available until the job succeeds; querying early returns `404` * Gzip compression is supported and worth using on large inputs * Webhook notifications on completion exist in beta, with substitutable placeholders like `${JOB_ID}` and `${JOB_STATUS}` **The tell:** if the geocoding result is written to a database rather than rendered to a screen, it should have been batched. ## Caching: the section that saves you money Do this before you evaluate any vendor. **Cache in your own datastore, not in a request-scoped memo.** Store the normalized address, coordinates, HERE ID, and a result-quality signal alongside the customer or location record. **Key on the normalized address, not the raw input.** `123 Main St` and `123 Main Street` should hit the same cache entry. **Set a long TTL.** Buildings persist. Rebuild the cache when HERE ships significant map updates, not on a two-week schedule because it felt prudent. **Store the confidence signal.** HERE returns result scoring and result types. A low-confidence match is a data quality problem you want surfaced, not silently persisted as truth. Instrument cache hit rate before you instrument anything else. A team with a 12% hit rate does not have a pricing problem. ## Production architecture **Normalize before you geocode.** Trimming, case-folding, and expanding abbreviations at write time increases cache hit rate for free. **Use result types to filter.** HERE distinguishes address results from place results and supports filtering by type. An address field that accepts a place result will store a shopping centre where a street address belonged. **Handle house-number fallback deliberately.** HERE supports falling back when an exact house number is unavailable. Whether that fallback is acceptable depends on whether you are shipping a parcel or scoring a site. Decide explicitly. **Country filtering narrows results and reduces ambiguity.** If your product operates in one market, say so in the request. **Political views matter for disputed territories.** HERE exposes political view handling. If you serve multiple markets, this is a legal and product decision, not a technical default. **Rate limits are commercial.** A persistent `429` means your quota does not match your workload. See [Authentication](/getting-started/authentication). ## Cost and usage considerations Each request is one billable transaction. Autocomplete fires on keystrokes — a naive implementation bills once per character typed. Where teams overspend: * **No cache.** The largest single item, every time. * **Autocomplete without debouncing.** Debounce at 200–300ms. Do not fire on every keypress. * **Real-time geocoding for overnight work.** Batch is cheaper per record. * **Autosuggest where autocomplete would do.** Different endpoints, different costs. * **Geocoding on every order for a stable customer address.** Geocoding-heavy workloads see the largest savings when moving from Google — typically 80%+ at production volume — but only after you have fixed the call pattern. Migrating an uncached, undebounced implementation just moves the waste to a cheaper meter. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Common mistakes **No persistent cache.** Fix this first, on whatever platform you are on. **Autocomplete on every keystroke.** Debounce. **Confusing `/autocomplete` with `/autosuggest`.** Addresses versus places. **Using real-time endpoints for batch workloads.** Use the Batch API. **Treating Batch API as a synchronous bulk endpoint.** It is a job lifecycle. Poll it. **Polling batch results before job completion.** `404` is expected, not an error to retry aggressively. **Storing a low-confidence match as truth.** Persist the score. **Accepting a place result in an address field.** Filter by result type. **Expecting Google-quality POI data.** You will not get it. Plan the hybrid. ## Best practices * Cache normalized address → coordinates in your own database, with a long TTL * Debounce autocomplete input * Batch anything that tolerates latency * Filter by result type and by country * Persist and act on the confidence score * Decide house-number fallback policy explicitly * Read the Batch API limits page before designing the job pipeline * Keep Google for consumer place discovery if that is your product ## API reference * [Introduction to HERE Geocoding & Search API v7](https://www.here.com/docs/bundle/geocoding-and-search-api-v7-api-reference/page/index.html) * [Endpoint list](https://www.here.com/docs/bundle/geocoding-and-search-api-v7-api-reference/page/index.html) * [Batch API v7 quick start](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/batch-api-quick-start.html) * [Batch API limits and performance](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/limits-and-performance.html) * [Result types](https://www.here.com/docs/bundle/geocoding-and-search-api-v7-api-reference/page/index.html) ## Related guides Coordinates to addresses — the telematics workload with its own failure modes. Geocoding is the smallest surface and the largest saving. Migrate it first. Why batch and cache decisions dominate your bill. Seven search endpoints, one correct choice per job. Also: [Authentication](/getting-started/authentication) · [HERE vs Google Maps](/comparisons/here-vs-google-maps) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial solutions. [Talk to us](https://placematic.com/contact/). # Migrating from Google Maps Platform to HERE Source: https://docs.placematic.com/guides/google-migration A sequenced migration guide — what to move first, what to keep on Google, how to validate quality during cutover, and how to model whether migration is worth it at all. # Migrating from Google Maps Platform to HERE Before anything else: **optimize on Google first.** Cache your geocoding results. Batch what tolerates latency. Debounce your autocomplete. Put a CDN in front of your tiles. Stop reverse-geocoding every GPS ping. Do that, re-measure, and a meaningful share of teams discover the bill halved without changing vendors. What remains is your real migration case — and now you have a clean baseline to measure against. Migrating an uncached, undebounced implementation to HERE moves the waste to a cheaper meter. You will save money and still be wrong. ## What problem this guide solves You have decided — or been asked — to evaluate leaving Google Maps Platform. This guide is about sequencing, validation, and honest cost modelling. It is not a cheerleading exercise. Several sections tell you not to migrate. ## When migration is justified Two or more of these should be true: * **You route commercial vehicles outside the contiguous United States, under axle-group weight regulation, or with toll cost in the dispatch decision.** Google added Large Vehicle Routing in August 2026 for the contiguous 48, so "Google can't do trucks" is no longer the argument. Coverage, toll cost, offline guidance and the constraint tail are. * **Monthly spend is five figures or more**, dominated by routing or geocoding. * **You compute large distance matrices** with sequential calls. See [Matrix Routing](/guides/matrix-routing). * **Price volatility is a board-level concern.** HERE pricing through a Gold Partner is contractual and does not reprice mid-term. * **You need truck-attributed map data** and are currently approximating it. ## When NOT to migrate Do not migrate if the only argument is "cheaper." Cheaper on a feature that regresses is not cheaper. * **Spend under roughly \$1,000/month.** Optimize call patterns instead. The payback period on migration engineering is measured in years. * **Your product's core value is consumer place discovery.** Google's POI database — business hours, reviews, photos, categories — is categorically better. Keep it. * **You need Street View.** No equivalent exists. * **No engineering capacity this quarter.** A half-finished migration is worse than either endpoint. * **Deep frontend coupling to `google.maps.*` with no other reason.** That is a rewrite of your rendering layer. ## The honest cost model Present this to your CFO before someone else asks for it. | Line | Value | | ----------------------------------------------- | ------------------------- | | Current Google spend, per endpoint, from logs | Measure it | | Savings from caching and batching **on Google** | Measure it | | Remaining spend — the real migration target | Derived | | Projected HERE spend at the same call volume | Model both pricing models | | Migration engineering cost | Estimate honestly | | Payback period | Derived | Teams migrating at production volume typically see **40–70% savings**, and **80%+ on geocoding-heavy workloads**. Those ranges assume the call pattern is already sane. A payback period beyond eighteen months means the savings argument is weaker than the headline number. Say so before your CFO does. It buys you credibility for the migrations that are worth doing. **The meters do not map one to one.** Google bills per session where HERE bills per request, and the reverse. You cannot derive a HERE forecast from a Google invoice. Instrument per-endpoint call counts first. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Sequencing: migrate one surface at a time Never migrate two systems simultaneously. Two independent changes, one production incident, ambiguous root cause. You will spend more on the investigation than you saved that month. **1. Batch geocoding.** Smallest surface. Largest saving. Nothing user-facing. If it goes wrong, you fix a nightly job, not a checkout flow. Start here. See [Geocoding](/guides/geocoding). **2. Reverse geocoding for telematics.** Same argument. Backend, high volume, no UI. See [Reverse Geocoding](/guides/reverse-geocoding). **3. Matrix operations.** If you are looping routing calls, this is where the money is. See [Matrix Routing](/guides/matrix-routing). **4. Routing.** Now user-visible. Requires quality validation, not just cost validation. See [Routing](/guides/routing). **4b. Truck routing, if you were on Google's Large Vehicle Routing.** A separate surface with its own defect class. Map `totalHeightMm` to `vehicle[height]` in centimetres, `travelMode=TRUCK` + `routeModifiers.vehicleInfo` to `transportMode=truck` + `vehicle[...]`, and re-map the hazardous goods taxonomy deliberately. Constraints Google could not express — weight per axle group, ADR tunnel category — have to be sourced from your fleet register, not inferred from the old request payload. **5. Real-time geocoding and autocomplete.** User-facing. Be honest about whether HERE's suggestions serve your users as well. **6. Rendering.** Last. Most visible. Tile aesthetics differ. See [Maps](/guides/maps). Stabilize each step before starting the next. ## What to keep on Google A hybrid architecture is a decision, not an admission. Say so up front, in writing, before someone frames it as a failed migration. Keep Google for: * Consumer place search and business POI data * Consumer autocomplete where typo tolerance and aliasing matter * Street View * Any surface where its data quality is the product Move to HERE: * Truck and commercial vehicle routing * Distance matrices * Batch and telematics geocoding * Transport-attributed map layers ## Mechanical differences that will bite you **Coordinate order.** HERE Routing takes `lat,lng`. GeoJSON is `lng,lat`. This will bite you exactly once, and it will be at 2am. **`200` is not success.** HERE returns `200` with an empty `routes` array and a `notice` containing `routeCalculationFailed` when no path exists. Checking `resp.ok` swallows the failure. See [Routing](/guides/routing). **`403` is not `401`.** `401` means a bad key. `403` means a valid key without entitlement for that API. Retrying `403` is permanently futile and generates support tickets. See [Authentication](/getting-started/authentication). **Vehicle units differ by a factor of ten.** HERE's `vehicle[height]` is centimetres. Google's large vehicle fields are millimetres — `totalHeightMm`. Convert at the adapter and assert the converted value in a test, not the input. A 41-centimetre truck routes everywhere and returns `200`. **Truck mode requires explicit dimensions.** `transportMode=truck` selects the engine. It does not describe your vehicle. Height in centimetres, `grossWeight` in kilograms. Omitting them returns a route no truck can drive, with no warning. **Matrix results are flat arrays.** `travelTimes` is row-major, not nested. Index as `travelTimes[origin * numDestinations + destination]`. Get it wrong and every travel time is plausible and every assignment is transposed. **Batch geocoding is a job lifecycle.** Create, start, poll, retrieve, delete. Not a bulk endpoint. **Response schemas differ throughout.** Mechanical work. Not hard work. Not zero. ## Validating quality, not just cost This is the step teams skip, and the reason migrations get reverted. **Replay real traffic.** Take 500 actual historical trips. Route them through both platforms. Compare against ground truth — actual driven distance and duration from telematics — not against each other. **For truck routing, test trap geometry.** Route a 410 cm vehicle through the 11foot8 bridge in Durham NC, Storrow Drive in Boston, and the Southern State Parkway on Long Island. Any platform that returns a path fails. Run the same three in `car` mode as a control; all three should route. **Shadow-write before you cut over.** Send production traffic to both. Log both responses. Compare offline. Cut over when the diff is boring. **Feature-flag by endpoint.** Not by user. Not by percentage. By endpoint, so a rollback is one flag and one surface. **Keep both live during cutover.** Two invoices for a month is cheaper than one incident. Write the rollback procedure before the cutover, and test it. A migration you cannot reverse is not a migration, it is a bet. ## Common mistakes **Migrating before optimizing.** You may be migrating waste. **Comparing per-1,000 list prices.** Entry rates are close across vendors. Call mix, volume tier, batching, and free bundles determine your bill. **Migrating everything at once.** Ambiguous root cause on the first incident. **Presenting savings without migration cost.** Your CFO will ask. **Validating cost but not quality.** Cheaper wrong routes are worse. **Assuming HERE POI data will satisfy a consumer search feature.** It will not. **Underestimating frontend coupling.** `google.maps.*` in React components is not a config change. **Treating tile styling as cosmetic.** Show design the maps early. **No rollback plan.** ## Best practices * Instrument per-endpoint call counts from logs, not estimates * Cache, batch, and debounce **on Google** first; re-measure * Sequence: batch geocoding → telematics → matrix → routing → autocomplete → rendering * Shadow-write and compare offline before cutting over * Validate route quality against telematics ground truth * Assert trap-geometry rejection in CI for truck routing * Feature-flag per endpoint; keep both platforms live during cutover * Document what stays on Google, and why, before anyone calls it a partial migration ## API reference * [Routing API v8](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/get-started.html) * [Geocoding & Search v7](https://www.here.com/docs/bundle/geocoding-and-search-api-v7-api-reference/page/index.html) * [Matrix Routing v8](https://www.here.com/docs/category/matrix-routing-api-v8) * [HERE Map Rendering](https://www.here.com/docs/category/here-map-rendering) ## Related guides Where each platform genuinely wins, including where Google does. Model the bill before you build the business case. The first surface to migrate, and the largest saving. Constraints, units, and the trap geometry that proves they apply. Also: [Matrix Routing](/guides/matrix-routing) · [Reverse Geocoding](/guides/reverse-geocoding) · [Maps](/guides/maps) · [HERE API pricing](https://placematic.com/here-location-services/here-pricing/) *** Teams migrating today often want agent access as well. The [MCP Server](https://placematic.com/mcp/) uses the same key and the same transactions. Want the numbers for your own volume before you migrate? [Get a free Cost Reduction Audit](https://placematic.com/here-location-services/cost-reduction-audit/) — your projected savings vs Google Maps, no sales call. Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial solutions. [Talk to us](https://placematic.com/contact/). # HERE SDK for iOS and Android — Implementation Guide Source: https://docs.placematic.com/guides/here-sdk Implementing the HERE mobile SDK — editions and entitlements, offline maps, on-device turn-by-turn, binary size, and the licensing question that surfaces at app store submission. # HERE SDK for iOS and Android The most important fact about the HERE SDK is commercial, not technical. **Web tile access and mobile SDK access are separate entitlements.** A contract that serves tiles to your web dispatcher does not automatically license the SDK in your driver app. Teams discover this during app store submission. It is expensive, it is late, and it is entirely avoidable. Confirm mobile SDK entitlement **before** you scope the mobile release. Not after the build succeeds. Both web and mobile are available under a single Placematic contract — but both must be enabled explicitly. See [Getting a HERE API Key](/getting-started/getting-a-here-api-key). ## What problem this guide solves A driver's phone loses signal in a canyon, a tunnel, or forty miles of rural Nevada. A REST routing call returns nothing. The navigation stops. The SDK exists because some things must run on the device: turn-by-turn guidance, offline maps, on-device rendering, position updates at a rate no network round trip can sustain. ## When to use the SDK * **Turn-by-turn navigation** in a driver-facing app * **Offline maps** where connectivity is unreliable * **On-device rendering** with tight performance requirements * **On-device route recalculation** when a driver deviates * **Continuous position updates** at a cadence unsuited to network calls * **Offline turn-by-turn for commercial vehicles has no equivalent on Google Maps Platform.** Google's Navigation SDK supports large vehicle routes via a route token issued by the Routes API, and it operates online. If your driver app has to guide a constrained vehicle through forty miles without signal, this is not a preference between platforms — it is the only one that does it. ## When NOT to use it * **Displaying a map on a web page.** Use tiles with Leaflet, OpenLayers, or MapLibre GL. See [Maps](/guides/maps). * **A dispatcher dashboard.** That is a web application. * **A read-only map in a mobile app.** A tile-based map view is lighter, simpler, and avoids the SDK entitlement question entirely. * **Calling routing from a mobile app.** The REST [Routing API](/guides/routing) works fine from a phone. The SDK is for guidance, not for HTTP. * **Anything where binary size is a hard constraint** and offline maps are not required. The SDK is not small. Ask yourself: does this app need to work with the network off? If no, you probably need tiles and REST calls, not the SDK. That answer saves an entitlement conversation and several megabytes. ## Key concepts **The SDK ships in editions with different capabilities and different licensing.** Navigation, offline maps, and rendering are not uniformly available across all of them. Which edition you need is a commercial question that determines your technical options, not the reverse. **Offline maps are downloaded regions, not a cache.** You choose what to download, when, and how much storage to consume. A driver crossing three states needs three regions, downloaded before departure, on Wi-Fi. **Offline map data has a release cadence.** A region downloaded in March reflects March's map. Refresh policy is your decision and it has bandwidth consequences. **Binary size is a real cost.** The SDK adds meaningfully to your app. On Android, this affects install conversion. Measure it before you commit. **Truck routing constraints apply on-device.** `height`, `grossWeight`, `axleCount`, `weightPerAxleGroup`, `tunnelCategory` — the same parameters as [Truck Routing](/guides/truck-routing). An on-device route computed without them is exactly as undrivable as a REST route computed without them, and now it is guiding a driver in real time. **Credentials are embedded in the binary.** Binaries are trivially decompiled. This is a security consideration, not a hypothetical. See [Authentication](/getting-started/authentication). ## Code examples HERE SDK initialization, credential handling, and API surface differ substantially across editions and platform versions, and are gated by entitlement. Publishing initialization code we cannot verify against your edition would be worse than publishing none. Start from HERE's maintained reference: * [HERE SDK documentation](https://www.here.com/docs/category/here-sdk) * [HERE Map Rendering documentation](https://www.here.com/docs/category/here-map-rendering) Placematic confirms your edition and provides working initialization during a pilot. ## Production architecture **Decide the offline strategy before you write code.** Which regions, downloaded when, refreshed how often, consuming how much storage. Retrofitting offline onto an online-first architecture is a rewrite. **Download on Wi-Fi, on a schedule.** A driver starting a shift on cellular should not be downloading a state. **Design for the online/offline transition.** Route computed online, driver enters a tunnel, route recalculated offline. The two engines can disagree. Decide which wins and make it deterministic. **Server-side routing and on-device routing serve different purposes.** Dispatch plans the route on the server, with full fleet context. The device guides along it and recalculates on deviation. Do not let the device silently re-plan a route that dispatch committed to a customer. **Vehicle profiles must reach the device.** Height, weight, axle count. Store them server-side, push them to the app, apply them to on-device routing. A profile that lives only in your dispatch backend produces safe server routes and unsafe device recalculations. **Test on the worst device your drivers actually use.** Not the newest phone in the office. The four-year-old Android tablet bolted to a dashboard. **Plan for map region storage on constrained devices.** Offline maps are large. So are photos of delivery confirmations. The single most dangerous pattern in mobile fleet software: server-side truck routing with constraints, device-side recalculation without them. The driver deviates, the phone reroutes, and the constraint set never made it into the app. Push vehicle profiles to the device. Test the recalculation path. ## Cost and usage considerations SDK licensing is a distinct entitlement from web tile access, and pricing structure differs from per-request REST billing. Offline map downloads consume bandwidth and may carry their own terms. Confirm before you build: * Which SDK edition your contract covers * Whether offline maps are included * How on-device navigation is metered * Whether your web tile entitlement extends to mobile rendering Do not assume your REST API contract covers the SDK. Do not assume your web tile contract covers mobile rendering. These are the two assumptions that produce a licensing conversation two weeks before launch. See [HERE Pricing Explained](/getting-started/here-pricing-explained) and the [HERE API pricing page](https://placematic.com/here-location-services/here-pricing/). ## Common mistakes **Assuming web tile entitlement covers the mobile SDK.** Discovered at submission. **Using the SDK when tiles and REST would serve.** Weight, complexity, and licensing for no benefit. **Shipping without on-device truck constraints.** Safe server routes, unsafe recalculations. **Retrofitting offline onto an online-first app.** **Downloading map regions on cellular.** **Ignoring binary size until the store rejects or the install rate drops.** **Letting the device silently re-plan a committed dispatch route.** **Embedding credentials without acknowledging that the binary is readable.** **Testing only on modern hardware.** **Treating an offline map as a cache with a TTL.** It is a downloaded region with a release version. ## Best practices * Confirm SDK edition and entitlement before scoping the mobile release * Choose tiles + REST unless you genuinely need offline or on-device guidance * Push vehicle profiles from server to device; apply them to on-device routing * Test the offline recalculation path against truck constraints * Download regions on Wi-Fi, on a schedule * Make the online/offline handoff deterministic * Measure binary size impact before committing * Test on the oldest device in the field * Separate dispatch planning from device guidance ## API reference * [HERE SDK documentation](https://www.here.com/docs/category/here-sdk) * [HERE Map Rendering documentation](https://www.here.com/docs/category/here-map-rendering) ## Related guides Web tiles, and the entitlement boundary between them and the SDK. Constraints that must reach the device, not just the server. Entitlements are per-product. Confirm before you build. Server-side planning, which the device guides along. Also: [Authentication](/getting-started/authentication) · [HERE SDK](https://placematic.com/here-location-services/here-sdk/) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial solutions. [Talk to us](https://placematic.com/contact/). # HERE Maps and Map Rendering — Implementation Guide Source: https://docs.placematic.com/guides/maps Implementing HERE map tiles in production — vector versus raster, web versus mobile SDK entitlements, session versus request billing, and the licensing question that surfaces at app store submission. # HERE Maps and Map Rendering Rendering is where mapping platforms feel most interchangeable and are most legally distinct. Two things routinely surprise teams. First, web tile access and mobile SDK access are **separate entitlements** — discovering this during app store submission is expensive. Second, tiles bill differently depending on how you render them, so two implementations that produce the same map produce different invoices. ## What problem this guide solves You need a map on a screen. You may need a route drawn on it, traffic overlaid, or truck restrictions visible. You need to know what it costs and whether you are licensed to ship it. ## What HERE offers HERE Map Rendering spans several products, and the naming does not make the distinction obvious: * **Vector tiles** — geometry rendered client-side. Styleable. Sharper at any zoom. Larger client-side cost. * **Raster tiles** — pre-rendered images. Simple. Fixed styling. Trivial to cache. * **HERE Maps API for JavaScript** — HERE's own web SDK. * **HERE SDK (iOS/Android)** — native rendering, offline maps, turn-by-turn. * **HERE Style Editor** — for customizing vector tile styling. Overlays available on top of the base map include traffic flow, traffic incidents, satellite imagery, and transport-specific layers such as weight and height restrictions. Placematic tiles work with **Leaflet, OpenLayers, and MapLibre GL**. If you already use one of these, integration is largely a tile URL swap — not a rewrite. If you are on the Google Maps JS SDK, the rendering layer is a rewrite. Budget for it. ## When to use HERE map rendering * Fleet dispatcher and driver-facing maps where truck restrictions must be visible * Logistics and transport UIs needing weight, height, and hazmat layers * Store locators and network maps — see [Pos-Eye](https://placematic.com/here-location-services/#maps) * Any product where tile spend at production volume is material * Products already on Leaflet, OpenLayers, or MapLibre GL ## When NOT to use it **Do not migrate rendering for aesthetics or for savings alone.** Tile styling differs visibly from Google. Your design team will notice, and rendering is the most user-visible surface you own. * **Street View or equivalent.** HERE has no comparable product. * **Consumer place-rich maps.** Google's POI density, business labels, and hours are better. If your map's value is the businesses on it, that is a Google map. * **Low-volume rendering.** Under modest tile volumes both platforms are effectively free. Migration cost dominates. * **Deep coupling to `google.maps.*`.** Migrate because routing correctness or cost demands it. Not because a slide deck did. Keeping Google for the consumer-facing map while moving routing and geocoding to HERE is a coherent architecture, not a compromise. ## Key concepts **Web tiles and mobile SDK are separate entitlements.** Both are available under a single Placematic contract. Both must be enabled explicitly. A key that serves tiles to your web app may return `403` from the mobile SDK. See [Authentication](/getting-started/authentication). **Billing varies with implementation.** Tiles may be metered per request or per session depending on whether you use the web SDK, a mobile SDK, or static maps. Two implementations rendering the same view can bill differently. Know which one you shipped before you forecast. **Traffic overlays are independent layers.** They bill separately from base tiles and can be composited onto any base style. Turning traffic on is a cost decision, not a styling one. **Vector styling is client-side work.** Vector tiles cost less bandwidth and render sharper, at the price of client CPU. On low-end Android devices in a driver's cab, that trade-off is not academic. **Map data ships on a release cadence.** Base map freshness is a data-release property, not a live feed. Traffic flow and incidents are near-real-time. These are different systems with different latencies. **Attribution is contractual.** HERE requires attribution. It is not optional and it is not a design suggestion. ## Code examples Tile URL formats, style parameters, and SDK initialization differ across HERE's rendering products and across your entitlement — web tiles, HERE Maps API for JavaScript, and the native SDKs each have distinct request patterns. Rather than publish a URL template that may be stale for your contract, we recommend starting from HERE's maintained reference: * [HERE Map Rendering documentation](https://www.here.com/docs/category/here-map-rendering) * [HERE Maps API for JavaScript](https://www.here.com/docs/category/here-map-rendering) * [HERE Style Editor](https://www.here.com/docs/category/here-map-rendering) Placematic provides working tile URLs and an OpenAPI spec scoped to your account during a pilot. If you are already rendering with Leaflet, OpenLayers, or MapLibre GL, the integration point is your existing tile source configuration. The library code does not change. ## Production architecture **Cache tiles at the edge.** Tiles are static per zoom, x, y, and style. A CDN in front of your tile requests is the single highest-leverage cost reduction in rendering, and it is available to you regardless of vendor. **Restrict browser-exposed keys by domain.** Any key in frontend JavaScript is public. If it has no domain restriction, it is now someone else's key and someone else's invoice. **Consider proxying tile requests.** A thin server-side proxy keeps the key private and gives you a cache layer and per-tenant metering. It costs latency. Decide deliberately. **Do not render what nobody sees.** Pre-fetching tiles for map areas users never pan to is pure spend. **Separate the base map from overlays.** Traffic and restriction layers should be toggleable, and their cost should be attributable. **Handle low-end devices.** Vector rendering on an old Android tablet mounted in a truck cab is a different performance problem than vector rendering on a dispatcher's desktop. ## Cost and usage considerations Rendering is metered independently of routing and geocoding. There is no bundle. Where teams overspend: * **No edge caching.** Fix this first, on any platform. * **Traffic overlay always on.** It is a separate meter. Most users do not need it always. * **Pre-fetching aggressively.** Tiles fetched and never viewed. * **Rendering a full interactive map where a static image would serve.** A thumbnail on a job card does not need a pannable map. * **One key across web and mobile.** Not a cost problem — an entitlement problem that becomes an outage. Confirm which billing model applies to your rendering implementation before you forecast. See [HERE Pricing Explained](/getting-started/here-pricing-explained) and the [HERE API pricing page](https://placematic.com/here-location-services/here-pricing/). ## Common mistakes **Assuming web tile access covers the mobile SDK.** Separate entitlements. Discovered at submission. **Shipping an unrestricted key in frontend JavaScript.** Public key, your bill. **Forecasting tile cost without knowing your billing model.** Session versus request is not a detail. **Leaving traffic overlays permanently enabled.** Separate meter. **No CDN in front of tiles.** Static assets, refetched. **Treating tile styling as cosmetic.** It is, until the product review. **Ignoring attribution requirements.** Contractual. **Rendering an interactive map where a static image suffices.** ## Best practices * Put a CDN in front of tile requests * Restrict browser keys by domain; consider a server-side proxy * Confirm web and mobile SDK entitlements before you plan the mobile release * Make traffic and restriction overlays toggleable and attributable * Test vector rendering on the worst device your users actually have * Show the maps to design before you commit to the migration * Comply with attribution ## API reference * [HERE Map Rendering documentation](https://www.here.com/docs/category/here-map-rendering) * [HERE Style Editor](https://www.here.com/docs/category/here-map-rendering) * [HERE SDK](https://www.here.com/docs/category/here-map-rendering) ## Related guides Returning a polyline to draw on the map you just rendered. Rendering is the most user-visible surface. Migrate it last. Session versus request billing, and why it matters here. Domain restriction, key exposure, and the `403` that means "not entitled." Also: [Truck Routing](/guides/truck-routing) · [HERE vs Google Maps](/comparisons/here-vs-google-maps) · [HERE Location Services](https://placematic.com/here-location-services/) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial solutions. [Talk to us](https://placematic.com/contact/). # HERE Matrix Routing — Implementation Guide Source: https://docs.placematic.com/guides/matrix-routing Implementing HERE Matrix Routing v8 — sync versus async, region definitions, when one call replaces ten thousand, and the loop that is costing you money right now. # HERE Matrix Routing If you are calling the Routing API in a loop to build a table of travel times, stop reading and open your billing dashboard. This page will pay for itself. Matrix Routing computes travel times, distances, and consumptions between many origins and many destinations. One request. Not N×M. ## What problem this guide solves Depot-to-stop assignment. Nearest-driver dispatch. Service area analysis. Delivery zone economics. Every one of these needs a table of travel times, and every one of them is routinely built by looping a point-to-point routing API until the invoice arrives. A 200×3,000 matrix is 600,000 routing calls. Or it is one matrix request. ## When to use Matrix Routing * Assigning jobs to the nearest available vehicle * Depot-to-stop distance tables feeding an optimizer * Service-area or catchment analysis * Comparing candidate warehouse locations by aggregate drive time * Any question of the form "travel time between all of these and all of those" ## When NOT to use it * **A single A→B route with a polyline.** Use [Routing](/guides/routing). Matrix returns times and distances, not turn-by-turn geometry. * **Ordering stops into an itinerary.** Matrix gives you the cost table. Tour Planning solves the ordering. Feeding a matrix into your own solver is legitimate; expecting matrix to do the solving is not. * **Real-time UI where the user waits.** Large matrices are asynchronous by design. Submit, poll, retrieve. ## The endpoint Matrix Routing v8 lives at `https://matrix.router.hereapi.com/v8`, with `/matrix` for submission. It supports both `apiKey` (query parameter) and Bearer token authentication. The OpenAPI specification is published at `https://matrix.router.hereapi.com/v8/openapi`. Read it before you build. It is authoritative in a way that any third-party guide, including this one, is not. The service operates in three modes, selected by the `regionDefinition` and `profile` parameters: | Mode | Selected by | Notes | | ------------ | ------------------------------ | -------------------------------------- | | **Flexible** | `regionDefinition` type | Full routing options; smaller ceilings | | **Region** | Bounded region (e.g. `circle`) | Larger matrices within a bounded area | | **Profile** | `profile` parameter | Predefined vehicle profiles | The capabilities and limits of each mode differ meaningfully. HERE documents the trade-offs in the [Matrix Routing v8 category](https://www.here.com/docs/category/matrix-routing-api-v8). ## Sync and async Matrix calculation is available synchronously and asynchronously. Async is the mode that matters at production scale. The flow, per HERE's specification: 1. `POST` the matrix request 2. Receive a `matrixId` and a `statusUrl` 3. Poll the `statusUrl` — **use the URL HERE returns, not one you construct** 4. On completion, retrieve the result The response contains `numOrigins`, `numDestinations`, and flat arrays `travelTimes` and `distances`. The result arrays are **flat and row-major**, not nested. A 2×2 matrix returns `travelTimes: [73, 1231, 983, 400]`. Index into it as `travelTimes[originIndex * numDestinations + destinationIndex]`. Getting this wrong produces a matrix that is silently transposed — every travel time is plausible, and every assignment is wrong. This failure mode is the reason matrix bugs survive code review. Nothing crashes. ## What are the size limits? Matrix size ceilings depend on the mode you select (flexible, region, or profile), on whether you call synchronously or asynchronously, and on your entitlement. Published figures vary and are frequently quoted out of context. Do not architect against a number from a blog post or a comparison table. Read the [OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi) and confirm the ceiling for your mode and your contract before you commit to a design. The practically important fact is directional and uncontested: matrix mode handles origin-destination sets at a scale where looping point-to-point routing is not merely more expensive but architecturally infeasible. ## Production architecture **Async is not a synchronous call with a wait.** Submit, get an ID, return control to your caller, poll on a worker. Wrapping this in a blocking facade with a 30-second timeout reproduces every problem async was designed to eliminate. **Persist the `matrixId`.** If your process dies mid-poll, you need to resume, not resubmit. Resubmitting bills again. **Follow `statusUrl` exactly as returned.** HERE explicitly warns against constructing it. It may redirect to the result on completion. **Handle `429` on submission.** Concurrent job limits exist. Back off; do not hammer. **Truck parameters apply.** Matrix accepts truck mode and vehicle constraints — the same `grossWeight`, `axleCount`, and dimensional parameters described in [Truck Routing](/guides/truck-routing). A truck matrix built from car travel times is wrong in exactly the way a car route is wrong for a truck. **Cache matrices keyed on the input set.** Depot locations rarely move. If neither the origins, the destinations, nor the departure time changed, the matrix did not change. ## Cost and usage considerations Matrix operations are where the cost gap against Google is widest, and where the gap against your own looping implementation is widest of all. Run the arithmetic before you optimize anything else: * Count your current routing calls that exist solely to populate a distance table * That number, divided by the matrices it should have been, is your multiplier Where teams overspend: * Rebuilding an unchanged depot matrix nightly * Computing a full matrix when a bounded region would have sufficed * Requesting `consumptions` when only `travelTimes` is consumed downstream * Submitting one matrix per vehicle when one matrix per depot would serve all of them See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Common mistakes **Looping the Routing API.** The reason this page exists. **Indexing the flat result array as if it were nested.** Silently transposed. Nothing errors. **Constructing the `statusUrl` instead of using the returned one.** **Blocking on an async job.** Timeouts, retries, duplicate submissions, double billing. **Losing the `matrixId` on process restart.** Resubmission is a full recharge. **Building a truck matrix in car mode.** Wrong times, wrong assignments, wrong dispatch. **Architecting against a size ceiling read from a comparison table.** Read the spec. **Requesting fields nothing downstream reads.** ## Best practices * Treat async as async: submit, persist the ID, poll on a worker * Follow `statusUrl` verbatim * Write one indexing helper for the flat arrays and unit-test it against a known 2×2 * Cache on the hash of the input set * Pass truck parameters when routing trucks * Request only the fields you consume * Confirm mode-specific size limits against the OpenAPI spec and your entitlement ## API reference * [Matrix Routing API v8](https://www.here.com/docs/category/matrix-routing-api-v8) * [OpenAPI specification](https://matrix.router.hereapi.com/v8/openapi) — authoritative on limits, modes, and parameters ## Related guides Point-to-point. What matrix is not. Vehicle constraints, which matrix also accepts. Routing versus matrix versus tour planning, decided by the question. Matrix is the single largest cost lever available to most fleet platforms. Also: [EV Routing](/guides/ev-routing) · [HERE vs Google Maps](/comparisons/here-vs-google-maps) · [Authentication](/getting-started/authentication) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial solutions. [Talk to us](https://placematic.com/contact/). # HERE Points of Interest — Implementation Guide Source: https://docs.placematic.com/guides/points-of-interest Working with HERE POI data — discover, browse, and lookup endpoints, truck-relevant POIs, and an honest assessment of where Google's place data is simply better. # HERE Points of Interest Start with the honest part. **For consumer place discovery — business names, opening hours, reviews, photos, categories — Google's data is better than HERE's. Not marginally. Categorically.** If your product answers "what's a good restaurant near me," keep Google for that surface. Placematic sells HERE. We would rather you know this now than discover it after a migration. ## Where HERE POI data is the right choice **Truck-relevant POIs.** Truck stops, weigh stations, rest areas, hazmat-approved parking, height-restricted structures, service areas with commercial vehicle access. This is data that exists in HERE's transport-attributed map and does not meaningfully exist in a consumer places database. **Your own locations.** If the POIs you care about are your stores, depots, or service points, the platform's public place database is irrelevant. You need a store locator over your own data — see [Pos-Eye](https://placematic.com/here-location-services/#maps). **Category-filtered proximity queries** where address-grade accuracy matters more than review counts. **Bundled economics.** If you are already licensing HERE for routing and geocoding, POI queries ride the same contract. A modest place-lookup need may not justify a second vendor relationship. ## When NOT to use it Do not migrate a consumer place-search feature to HERE to save money. Your users will notice the missing hours, thinner categories, and absent reviews. Cost savings on a feature people stop using is not savings. * **Consumer restaurant, retail, or entertainment discovery.** Google. * **Anything depending on reviews, ratings, or photos.** HERE has no comparable product. * **Business hours as a primary feature.** Coverage and freshness differ. * **Street View or equivalent imagery.** Does not exist. * **Containment checks against your own zones.** That is a spatial query in your own database, not a POI lookup. See [Catchment Area](/guides/catchment-area). ## The endpoints POI access lives in HERE Geocoding & Search v7. Three endpoints, three different jobs. | Endpoint | Job | | ----------- | -------------------------------------------------------------------------------------------------- | | `/discover` | Free-text query. Results ranked by **relevance**. "coffee near Wrigley Field" | | `/browse` | Filter by category or name. Results ranked by **distance** from a centre. "pharmacies within 2 km" | | `/lookup` | Retrieve a known place by its HERE ID | `/discover` ranks by relevance. `/browse` ranks by distance. Choosing wrong produces a store finder whose nearest store is third in the list, or a search box that returns the closest match instead of the best one. This is the most common POI integration bug. `/autosuggest` also returns place candidates and handles misspellings — see [Geocoding and Search](/guides/geocoding) for how it differs from `/autocomplete`. Confirm the base host for your account against HERE's endpoint documentation before hardcoding it. Hosts differ by product and have changed across versions. We do not reproduce one here rather than risk publishing a stale value. ## Key concepts **Every place has a HERE ID.** Store it. Re-searching for a place you have already resolved is the POI equivalent of not caching geocoding results. Use `/lookup` when you have the ID. **Category taxonomies do not map across vendors.** HERE's categories are not Google's categories with different names. A migration that assumes a mapping table will produce silently wrong filters. **Result types are filterable.** A query near a shopping centre may return the centre, not a street address. If you need one and not the other, filter explicitly. **Spatial filters change the question.** A circular filter around a centre point is not the same as a bounding box, and neither is the same as "along this route." Choose deliberately. **Truck POI attributes come from the transport map.** They are not a category filter over consumer places. If you need weigh stations and truck parking, that is what you are licensing. **POI freshness is a data-release property.** Base map data ships on a release cadence. A restaurant that opened last week is not in this week's map. ## Code examples Geocoding & Search v7 host, parameter names, and category identifiers are versioned. We do not publish requests against a host we have not verified for your entitlement. Start from HERE's maintained reference: * [HERE Geocoding & Search v7 API reference](https://www.here.com/docs/bundle/geocoding-and-search-api-v7-api-reference/page/index.html) * [HERE Geocoding & Search documentation](https://www.here.com/docs/category/geocoding-search-api-v7) Authentication follows [Authentication](/getting-started/authentication). ## Production architecture **Cache place records by HERE ID.** Places move slowly. Your users search for the same ones repeatedly. **Debounce user-facing search.** A search box that fires on every keystroke bills once per character. 200–300ms. **Do not use POI search for containment.** "Is this address in our delivery zone" is a `ST_Contains` query against a stored polygon, not a places query. See [Catchment Area](/guides/catchment-area). **Separate your own locations from public POIs.** Your stores belong in your database. Querying a public place index for them is fragile and expensive. **For truck stop planning, join POIs to routes, not to circles.** "Truck stops within 10 km of the driver" is a different question from "truck stops along the remaining route," and the second one is the one dispatchers ask. **Model the taxonomy explicitly if you support both vendors.** Do not let category strings flow through untranslated. The hybrid architecture that works: HERE for routing, geocoding, and truck-relevant POIs. Google for consumer place discovery. Two vendors, two contracts, each doing what it is good at. This is a decision, not a compromise — document it as such before someone calls it a partial migration. ## Cost and usage considerations Each POI request is a billable transaction. `/discover` and `/browse` fire on user interaction, which means volume tracks engagement, not orders. Where teams overspend: * **Undebounced search boxes.** One transaction per keystroke. * **Re-searching for places already resolved.** Cache the HERE ID; use `/lookup`. * **Using `/discover` where `/browse` would serve.** Different endpoints, different costs. * **Querying public POIs to find your own locations.** * **POI search on map pan.** Every drag fires a query. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Common mistakes **Migrating consumer place search to HERE for cost reasons.** The feature regresses. Users leave. **Using `/discover` for a store finder.** Relevance ranking, not distance. The nearest store appears third. **Using `/browse` for a free-text search box.** Distance ranking, not relevance. **Assuming category taxonomies map across vendors.** **Not caching by HERE ID.** **Undebounced keystroke search.** **Using POI queries for zone containment.** **Expecting reviews, photos, or reliable hours.** **Querying a public place index for your own store network.** ## Best practices * Be explicit about which surfaces stay on Google, and why * Cache place records by HERE ID; use `/lookup` for known places * Debounce user-facing search at 200–300ms * Choose `/discover` for relevance, `/browse` for distance — deliberately * Filter by result type and by country * Keep your own locations in your own database * Join truck POIs to the route, not to a radius * Map category taxonomies explicitly if you run both vendors ## API reference * [HERE Geocoding & Search v7 API reference](https://www.here.com/docs/bundle/geocoding-and-search-api-v7-api-reference/page/index.html) * [HERE Geocoding & Search documentation](https://www.here.com/docs/category/geocoding-search-api-v7) ## Related guides Autocomplete versus autosuggest, and the caching that halves your bill. The place-data gap, stated plainly. Containment is a spatial query, not a POI search. Truck-relevant POIs come from the same transport-attributed map. Also: [Maps](/guides/maps) · [Points of Interest](https://placematic.com/here-location-services/points-of-interest/) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial solutions. [Talk to us](https://placematic.com/contact/). # HERE Positioning — Implementation Guide Source: https://docs.placematic.com/guides/positioning Hybrid positioning with HERE — Wi-Fi, cellular, and GNSS fusion, when GPS is not enough, and why most fleet platforms should not use this API at all. # HERE Positioning HERE Positioning determines a device's location from surrounding radio signals — Wi-Fi access points, cellular towers, Bluetooth beacons — rather than from GNSS satellites. It exists for one reason: GPS fails indoors, in urban canyons, in parking structures, and in warehouses. Most fleet platforms should not use it. ## What problem this guide solves A trailer enters a distribution centre. GPS degrades to nothing. For the next ninety minutes, your telematics feed reports the last known position — outside, on the road — while the trailer is at dock 14. Or: a warehouse operator wants to know which aisle a forklift is in. GPS gives an accuracy radius that spans the building. Before reaching for this API, confirm the problem is actually a positioning problem. If your vehicles operate outdoors on public roads with clear sky view, GPS is more accurate, free, and already in the device. Adding hybrid positioning solves nothing and bills for the privilege. ## When to use HERE Positioning * Asset tracking inside warehouses, distribution centres, and terminals * Devices without a GNSS chip — low-cost tags, some IoT sensors * Dense urban environments where multipath makes GPS unreliable * Indoor/outdoor transition tracking, where continuity matters more than absolute precision * Fallback positioning when GNSS is unavailable or has not yet acquired a fix * Reducing time-to-first-fix on cold start ## When NOT to use it * **Outdoor road vehicles with GPS.** This is the majority of fleet telematics. GNSS is better and already paid for. * **Determining which road a vehicle drove.** That is [Route Matching](/guides/route-matching). * **Turning a coordinate into an address.** That is [reverse geocoding](/guides/reverse-geocoding). * **Sub-metre indoor precision.** Radio positioning does not reliably deliver it. If you need aisle-and-shelf accuracy, you need a purpose-built RTLS with dedicated infrastructure. * **Anywhere Wi-Fi and cell density is low.** Rural highways. Radio positioning degrades exactly where GPS works best, and improves exactly where GPS fails. That inverse relationship is the whole product. ## Key concepts **Accuracy varies by radio environment, not by configuration.** A dense office with fifty visible access points positions well. A steel warehouse with three positions poorly. You cannot tune your way out of a sparse radio map. **The device does the scanning; the API does the resolution.** Your application collects observed access points and cell towers, submits them, and receives a position estimate. The quality of what you submit determines the quality of what you get. **Every position carries an uncertainty radius.** Treat it as part of the answer, not as metadata. A position with a 200-metre radius is not a location — it is a constraint. Persisting it as a point coordinate discards the only honest thing in the response. Storing a hybrid position as a bare `(lat, lng)` pair, without its accuracy radius, converts a probabilistic estimate into a fact. Every downstream consumer will treat it as GPS-equivalent. This is the most common and most damaging mistake with this API. **Radio environments change.** Access points are added, removed, and moved. A position that was accurate last quarter may not be today. This is not a bug in the API. **Privacy is a design constraint, not an afterthought.** Radio scans reveal where a device is and, in aggregate, where a person has been. Whatever your legal obligations are, this data raises them. ## Code examples HERE Positioning's request format — the structure for submitting observed Wi-Fi access points, cell towers, and beacons — is versioned and entitlement-dependent. We do not publish a request we cannot verify against your account. Start from HERE's maintained reference: * [HERE Positioning documentation](https://www.here.com/docs/category/here-positioning) Authentication follows the pattern in [Authentication](/getting-started/authentication). **Ignore any Placematic-hosted positioning endpoint you may have seen on a marketing page.** There is no `positioning.placematic.com`. Placematic issues HERE credentials; you call HERE's endpoints. If you encounter a Placematic API host in any material, treat it as an error and tell us. ## Production architecture **Fuse on device, not in the cloud.** The device knows its GNSS fix quality in real time. Deciding *there* whether to fall back to radio positioning avoids a round trip and avoids paying for a call you did not need. **Trigger on GNSS degradation, not on a timer.** Positioning when GPS is healthy is pure spend. Positioning at a fixed interval regardless of GPS state is the same mistake as [reverse-geocoding every ping](/guides/reverse-geocoding). **Cache the radio fingerprint.** A forklift parked at dock 14 sees the same access points for an hour. If the observed set has not changed materially, the position has not changed. Do not resubmit. **Persist the accuracy radius alongside the coordinate.** Every downstream system that reads position must be able to distinguish a 5-metre GNSS fix from a 300-metre radio estimate. **Model the transition.** Indoor/outdoor handoff produces position jumps. A vehicle "teleporting" 80 metres as it exits a building is the positioning system working correctly and the UI working badly. Smooth the display; do not smooth the stored data. **Do not feed hybrid positions into [Route Matching](/guides/route-matching).** Map matching assumes GNSS-grade observations. Radio positions inside a building will match to the nearest road, confidently and wrongly. **Batch where nothing waits.** Historical enrichment of stored radio scans is a batch job. ## Cost and usage considerations Each positioning request is a billable transaction, under its own entitlement. A key that routes does not necessarily position. See [Getting a HERE API Key](/getting-started/getting-a-here-api-key). Where teams overspend: * **Positioning on a fixed interval regardless of GPS state.** The dominant waste. Trigger on GNSS failure. * **Resubmitting an unchanged radio fingerprint.** Idle assets bill continuously. * **Positioning outdoor road vehicles.** They have GPS. It is free and better. * **Positioning every device in a facility when a gateway-level fix would suffice.** One fix per dock, not one per pallet. The economics only work when positioning is the exception. If it is your primary source of location, examine whether GNSS-capable hardware is cheaper than the API bill. Often it is. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Common mistakes **Using it as a GPS replacement rather than a GPS fallback.** **Discarding the accuracy radius.** The single most damaging error. **Positioning on a timer instead of on GNSS degradation.** **Resubmitting identical radio fingerprints for stationary assets.** **Expecting sub-metre indoor accuracy.** Wrong technology. **Feeding radio positions into map matching.** Confident nonsense. **Deploying in a rural environment.** Sparse radio, sparse accuracy. **Assuming your routing key positions.** Separate entitlement. **Treating radio scan data as ordinary telemetry** for privacy and retention purposes. **Copying a `positioning.placematic.com` example from a marketing page.** It does not exist. ## Best practices * Use positioning as a fallback, triggered by GNSS degradation * Persist and propagate the accuracy radius with every stored position * Cache the radio fingerprint; suppress resubmission when it is unchanged * Fuse on device where possible * Smooth indoor/outdoor transitions in the UI, not in storage * Never feed hybrid positions to map matching * Confirm positioning entitlement before you architect around it * Treat radio scans as sensitive data ## API reference * [HERE Positioning documentation](https://www.here.com/docs/category/here-positioning) Complete request schemas, supported radio types, and response fields are maintained by HERE. ## Related guides What to do with GNSS traces — and what never to do with radio positions. Turning a coordinate into an address, which positioning does not do. Positioning is a separate entitlement. Confirm it first. Why positioning economics only work as an exception path. Also: [Authentication](/getting-started/authentication) · [HERE Positioning](https://placematic.com/here-location-services/here-positioning/) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial solutions. [Talk to us](https://placematic.com/contact/). # HERE Reverse Geocoding — Implementation Guide Source: https://docs.placematic.com/guides/reverse-geocoding Implementing HERE reverse geocoding for telematics and fleet platforms — multi-reverse geocoding, drive-direction snapping, and the GPS ping problem that quietly destroys budgets. # HERE Reverse Geocoding Reverse geocoding converts coordinates into the nearest address. It is conceptually the simplest endpoint on the platform and, for fleet and telematics products, the most reliably mis-implemented. The reason is structural: GPS pings arrive at a fixed rate whether anyone needs an address or not. ## What problem this guide solves A vehicle emits a position every ten seconds. Over a nine-hour shift that is 3,240 pings. Across 200 vehicles, 648,000 pings a day. If your platform reverse-geocodes each one, you are billing 648,000 transactions daily to answer a question almost nobody asked. The address is needed when a human looks at a screen, when a stop is detected, or when a report is generated. Not on arrival of every packet. ## When to use reverse geocoding * Rendering a human-readable location for a selected vehicle * Labelling detected stops, arrivals, and departures * Enriching telematics events for reports or compliance records * Geofence entry and exit annotation * Turning a map click into an address ## When NOT to use it Do not reverse-geocode raw GPS pings on ingestion. This is the most expensive default in fleet software. * **Every telematics ping.** Geocode on demand, or on stop detection. Not on ingest. * **Snapping a trace to the road network.** That is Route Matching, a different API. Reverse geocoding gives you an address, not a road segment. If an auditor asks which road the vehicle travelled, reverse geocoding cannot answer. * **Coordinates you already resolved.** Vehicles idle. A hundred pings at the same depot resolve to the same address. * **Determining whether a point is inside a zone.** That is a spatial containment problem, solved in your own database with a polygon, not by resolving an address and comparing strings. ## The endpoints | Endpoint | Job | | ------------------------ | ---------------------------------------------------------------- | | `GET /revgeocode` | Nearest address to one coordinate pair | | `POST /multi-revgeocode` | Addresses or places for a **list** of coordinates in one request | `/multi-revgeocode` exists precisely for the telematics case. If you are looping `/revgeocode` over a batch of pings, you are making the same mistake as looping routing to build a matrix. HERE documents this endpoint under [Geocoding & Search v7](https://www.here.com/docs/bundle/geocoding-and-search-api-v7-api-reference/page/index.html). Both belong to Geocoding & Search v7. For batch workloads with no latency requirement, the [Batch API v7 supports reverse geocoding](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/reverse-geocoding.html) as a job — cheaper per record than real-time. Confirm the base host for your account against HERE's endpoint documentation before hardcoding it. We do not reproduce it here rather than risk publishing a stale value. ## Key concepts **A returned address is a nearest match, not a truth.** The coordinate sits *somewhere*. HERE returns the closest addressable feature. In a rural area that may be several hundred metres away and on a different property. **Drive direction improves street matching.** HERE supports considering the vehicle's heading when snapping to a street. On a divided highway, a coordinate 15 metres from the centreline can resolve to either carriageway. Heading disambiguates. For fleet products this materially reduces "the truck is on the wrong side of the interstate" support tickets. **Micro Point Address snapping improves accuracy** where that data exists. HERE documents this as a distinct capability. **Spatial filters limit results.** A circular filter constrains candidates to a radius. Without one, a coordinate in the ocean returns something eventually. **Result types are filterable.** A coordinate near a shopping centre may resolve to the place, not the street address. If you need a street address, filter for one. **Political views apply here too.** Disputed territory labelling is a product and legal decision. ## Production architecture **Detect stops, then geocode.** The event worth an address is "vehicle stationary for four minutes," not "vehicle emitted a packet." One address per stop, not 24 per stop. **Geocode lazily on view.** A dispatcher watching one vehicle needs one address. Do not pre-resolve the other 199. **Cache by rounded coordinate.** Round to roughly 5 decimal places — about one metre — and cache. A vehicle idling at a depot generates hundreds of pings within a metre of each other. One lookup. **Cache depot and customer locations permanently.** They are known, fixed, and finite. Resolve them once at onboarding. **Batch the historical backfill.** Enriching six months of trip history is a job, not a stream of real-time calls. **Pass heading where telematics provides it.** Free accuracy. Instrument the ratio of reverse-geocoding calls to detected stops. Anything above roughly 1.5 means you are geocoding pings, not events. ## Cost and usage considerations Each reverse-geocode request is one billable transaction. `POST /multi-revgeocode` resolves a list in one request — check how your entitlement meters it before assuming it is one transaction for the whole list. Where teams overspend: * **Geocoding every ping on ingest.** The dominant cost, by an order of magnitude. * **Looping `/revgeocode` instead of using `/multi-revgeocode`.** * **No coordinate rounding or caching.** Idle vehicles bill continuously. * **Real-time calls for historical backfill.** Use the Batch API. * **Re-resolving fixed depot coordinates.** This is the workload where geocoding-heavy savings versus Google reach their maximum — but only after the call pattern is fixed. Migrating an ingest-time reverse geocoder to HERE reduces the bill without fixing the architecture. Fix the architecture first; the platform decision is easier afterwards. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Common mistakes **Reverse-geocoding on packet ingest.** Geocode events, not packets. **Looping single-coordinate calls over a list.** Use `/multi-revgeocode`. **No coordinate rounding before cache lookup.** Sub-metre GPS jitter defeats an exact-match cache. **Ignoring heading.** Wrong carriageway, wrong street, support ticket. **Using reverse geocoding to snap a GPS trace to roads.** That is Route Matching. **Using reverse geocoding for geofence containment.** That is a spatial query. **Storing the nearest address as the vehicle's true location.** It is a nearest match. Store the coordinate too. **Real-time endpoints for historical enrichment.** Batch it. ## Best practices * Trigger on stop detection, not on ping arrival * Use `POST /multi-revgeocode` for lists * Round coordinates to \~5 decimal places and cache * Resolve depots and customer sites once, permanently * Pass drive direction when available * Apply spatial filters and result-type filters * Batch all historical enrichment * Persist the original coordinate alongside the resolved address ## API reference * [Reverse geocode endpoint](https://www.here.com/docs/bundle/geocoding-and-search-api-v7-api-reference/page/index.html) * [Multi-reverse geocode](https://www.here.com/docs/bundle/geocoding-and-search-api-v7-api-reference/page/index.html) * [Batch API reverse geocoding](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/reverse-geocoding.html) * Drive-direction and Micro Point Address snapping are documented within the Geocoding & Search v7 developer guide ## Related guides The forward direction, plus autocomplete, autosuggest, and batch jobs. The other half of most fleet integrations. Why telematics reverse geocoding dominates so many bills. Fix the call pattern before you change vendors. Also: [Authentication](/getting-started/authentication) · [Choosing the Right HERE APIs](/getting-started/choosing-the-right-here-apis) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial solutions. [Talk to us](https://placematic.com/contact/). # HERE Route Matching — Implementation Guide Source: https://docs.placematic.com/guides/route-matching Map-matching GPS traces to the road network with HERE — why reverse geocoding cannot do this, IFTA and compliance workloads, and the difference between where a vehicle was and where it drove. # HERE Route Matching Route Matching takes a sequence of noisy GPS positions and returns the road segments the vehicle actually travelled. That is a different question from "what is the nearest address to this coordinate," and the distinction is the whole guide. ## What problem this guide solves A GPS trace is a cloud of points with 5–15 metres of error, sampled at intervals during which a vehicle may have travelled several hundred metres. On a divided highway, adjacent points can appear on opposite carriageways. At an interchange, a point can sit between three roads. You need to know which road. Not which address — which **road segment**, in sequence, with the attributes attached to it. [Reverse geocoding](/guides/reverse-geocoding) resolves a coordinate to a nearby address. It cannot tell you which road the vehicle drove, and it cannot survive an audit. These are different APIs solving different problems, and conflating them is common. ## When to use Route Matching * **IFTA reporting** — miles driven per jurisdiction, defensible under audit * **ELD and hours-of-service** — reconstructing the driven route from telematics * **Speed compliance** — comparing observed speed against the posted limit on the *actual* segment * **Toll reconciliation** — determining whether a tolled segment was used * **Trip reconstruction after an incident** * **Attributing driven distance to road classes** for cost modelling * **Cleaning noisy traces before analytics** ## When NOT to use it * **Displaying a vehicle's current position.** A marker on a map does not need map matching. * **Getting a human-readable location.** That is [reverse geocoding](/guides/reverse-geocoding). * **Planning a route.** Route Matching is retrospective. [Routing](/guides/routing) is prospective. * **Geofence entry/exit detection.** That is a spatial containment test against your own polygons. * **Traces with very sparse sampling.** Ten-minute intervals across a city leave the matcher guessing between many plausible paths. Fix the sampling rate first, or accept that the output is an inference. ## Key concepts **Map matching is inference, not lookup.** The algorithm chooses the most probable path given the observations and the network topology. It can be wrong. It is more likely to be wrong where the network is dense, the sampling is sparse, and the GPS error is large — which is to say, downtown. **Sampling rate is the dominant quality input.** More frequent positions constrain the possible paths. This is a telematics configuration decision that determines your matching quality before any API is called. **The output is segments with attributes.** Speed limits, functional class, and other road attributes attach to the matched segments. This is what makes compliance workloads possible, and it is what reverse geocoding cannot give you. **Speed compliance requires the matched segment.** Comparing a GPS-derived speed against the limit on the *nearest* road, rather than the *travelled* road, produces false violations on service roads parallel to highways. Drivers notice. So do lawyers. **Trace direction matters.** Heading disambiguates carriageway. If your telematics feed includes it, provide it. **Order is meaningful.** A trace is a sequence, not a set. Points must be supplied in temporal order, with timestamps. ## Code examples HERE's map matching capability is delivered through the Routing product family, with a distinct request pattern for trace submission. Endpoint paths and trace formats are versioned. Rather than publish a request we cannot verify against your entitlement, start from HERE's maintained reference: * [HERE Routing documentation](https://www.here.com/docs/category/routing-api-v8) * [Routing API v8 reference](https://docs.here.com/routing/reference/routing-api-v8-calculateroutes) Placematic scopes a working trace-matching request against your telematics format during a pilot. Authentication follows the pattern in [Authentication](/getting-started/authentication). ## Production architecture **Match on trip close, not on ping arrival.** A trip is the unit. Matching a partial trace and then re-matching it as points arrive burns transactions and produces inconsistent history. **This is a batch workload.** Nothing is waiting. Match yesterday's trips overnight. Latency is worth nothing to you here, and it costs money. **Store the matched result, not just the raw trace.** Re-matching for a report six months later bills again and may produce a different answer against an updated map. **Store the raw trace too.** When an auditor disputes the matched path, you need the observations. **Version your matched output against the map release.** A trip matched in March against a March map may match differently in September. For compliance records, that matters. Record which map release produced the result. **Fix your sampling rate before you tune anything else.** No API compensates for a ten-minute ping interval. **Handle low-confidence matches explicitly.** A trace through a dense interchange may have several plausible interpretations. Surface the uncertainty rather than persisting a guess as fact. For IFTA specifically: the defensible artifact is raw trace + matched segments + map release version + timestamp of matching. Anything less is an assertion. ## Cost and usage considerations Route Matching bills as a routing-family transaction, and traces are large. Where teams overspend: * **Matching on every ping.** Match trips, not packets. This mirrors the [reverse geocoding](/guides/reverse-geocoding) failure exactly. * **Re-matching for every report.** Store the result. * **Real-time matching for retrospective reporting.** Batch it overnight. * **Matching idle time.** A vehicle parked for four hours emits thousands of points that describe a parking space. Segment trips first, then match the moving portions. * **Sub-second sampling submitted raw.** Downsample before matching. Beyond a certain density, additional points add cost and no information. The trip-segmentation-before-matching step routinely removes 40–60% of submitted points in real telematics feeds. Do it in your pipeline, not in HERE's. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Common mistakes **Using reverse geocoding to snap traces to roads.** It returns addresses. Audits ask about road segments. **Matching packets instead of trips.** **Submitting the parked portion of a trace.** Segment first. **Ignoring sampling rate and blaming the matcher.** Garbage in. **Real-time matching for overnight reports.** **Not persisting the raw trace.** The matched output is a derivation. Keep the source. **Not recording the map release version** on compliance records. **Comparing GPS speed against the nearest road's limit** rather than the matched segment's. False violations on frontage roads. **Treating a low-confidence match as certain.** ## Best practices * Segment trips before matching; drop idle periods * Downsample dense traces before submission * Match as a batch job on trip close * Persist raw trace, matched segments, map release, and match timestamp * Provide heading when telematics supplies it * Fix sampling rate at the device before tuning anything upstream * Surface low-confidence matches rather than persisting them silently * Use matched segments — not nearest roads — for speed compliance ## API reference * [HERE Routing API v8](https://www.here.com/docs/category/routing-api-v8) * [Routing API v8 reference](https://docs.here.com/routing/reference/routing-api-v8-calculateroutes) ## Related guides Coordinates to addresses. The API teams reach for when they need this one. The prospective half of most ELD integrations. Trace volume is the largest input to telematics API spend. Retrospective versus prospective, address versus segment. Also: [Positioning](/guides/positioning) · [Authentication](/getting-started/authentication) · [Route Matching API](https://placematic.com/here-location-services/here-route-matching/) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial solutions. We have deployed HERE into production ELD systems. [Talk to us](https://placematic.com/contact/). # HERE Routing API — Implementation Guide Source: https://docs.placematic.com/guides/routing How to implement HERE Routing v8 in production — transport modes, request shaping, cost control, and the mistakes that surface only after launch. # HERE Routing API The Routing API answers one question: how does a vehicle get from A to B, and how long will it take. It does not answer "what is the travel time between these 200 depots and 3,000 stops" — that is [Matrix Routing](/guides/matrix-routing). It does not answer "what order should I visit these stops in" — that is Tour Planning. Confusing the three is the most expensive mistake in this domain, and it is made in week one. ## What problem this guide solves You have coordinates. You need a path, a duration, and a distance. You need them to be correct for the vehicle you are actually dispatching, and you need the bill to be predictable. HERE's reference documentation tells you what every parameter is called. This guide tells you which ones you cannot afford to omit, and why the response looked fine in staging. ## When to use HERE Routing * Point-to-point navigation with turn-by-turn instructions * ETA calculation with real-time or predictive traffic * Commercial vehicle routing where physical constraints matter — see [Truck Routing](/guides/truck-routing) * Route shaping through intermediate waypoints * Rendering a route polyline on a map — see [Maps](/guides/maps) ## When NOT to use it If you are writing a loop around the Routing API, you have chosen the wrong product. You are building either a matrix or an optimizer, and both exist as separate, cheaper, faster APIs. * **Many-to-many travel times.** Use [Matrix Routing](/guides/matrix-routing). N×M routing calls is slower and costs more. * **Multi-stop optimization with time windows, capacities, or driver shifts.** Use Tour Planning. It is a solver, not a router. * **Snapping a GPS trace to roads.** That is Route Matching, a different API. * **Consumer place discovery.** Google's POI data is better and you should keep it. See [HERE vs Google Maps](/comparisons/here-vs-google-maps). ## Your first request ```bash cURL theme={null} curl -gX GET 'https://router.hereapi.com/v8/routes?'\ 'transportMode=car&'\ 'origin=52.5308,13.3847&'\ 'destination=52.5264,13.3686&'\ 'return=summary&'\ 'apiKey=YOUR_API_KEY' ``` ```python Python theme={null} import os, requests resp = requests.get( "https://router.hereapi.com/v8/routes", params={ "transportMode": "car", "origin": "52.5308,13.3847", "destination": "52.5264,13.3686", "return": "summary", "apiKey": os.environ["HERE_API_KEY"], }, timeout=10, ) resp.raise_for_status() summary = resp.json()["routes"][0]["sections"][0]["summary"] print(summary["duration"], summary["length"]) ``` ```javascript Node.js theme={null} const params = new URLSearchParams({ transportMode: "car", origin: "52.5308,13.3847", destination: "52.5264,13.3686", return: "summary", apiKey: process.env.HERE_API_KEY, }); const resp = await fetch(`https://router.hereapi.com/v8/routes?${params}`); if (!resp.ok) throw new Error(`HERE returned ${resp.status}`); const { routes } = await resp.json(); console.log(routes[0].sections[0].summary); ``` Coordinates are `lat,lng`. Not `lng,lat`. If you are migrating from a GeoJSON-based system, this will bite you exactly once. ## Key concepts **Transport mode selects an engine.** `car`, `truck`, `pedestrian`, `bicycle`, `scooter`, `bus`, `privateBus`, `taxi`. Each has a different traversable network and different legal turn rules. `truck` is not `car` with a speed penalty. Some modes carry beta status with limited functionality. Check the [transport modes reference](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/topics/transport-modes.html) before you build a product on one. **`return` controls both response size and cost of processing.** Requesting `polyline,actions,instructions,turnByTurnActions` when you needed `summary` inflates payloads by orders of magnitude. Set it explicitly. Always. **Waypoints use structured strings.** Values like `nameHint` can contain `&` and `!`, which collide with URL control characters. Encode them. HERE's own docs flag this as a common failure. **A failed route calculation returns HTTP 200.** If no path exists — the classic case being an ocean between origin and destination — you get a `notice` array with `routeCalculationFailed` and an empty `routes` array. Your error handling must check `routes.length`, not just the status code. Checking only `resp.ok` will silently swallow route failures. `200` with `"routes": []` is a valid HERE response. Handle it. ## Production architecture **Set a timeout.** Routing calls with many waypoints and traffic enabled are not instantaneous. An unbounded client timeout in a dispatcher UI turns into a hung page. **Cache route geometry, not ETAs.** The path between two fixed points is stable for hours. The ETA is not. Cache them separately with different TTLs, or you will serve stale arrival times to drivers. **Decide where traffic matters.** Departure-time routing with live traffic is correct for dispatch. It is wasteful for a "how far is this customer" screen that nobody acts on. **Idempotency and retries.** `5xx` is transient — retry with exponential backoff and jitter. `403` is an entitlement failure and will never succeed. See [Authentication](/getting-started/authentication). **Rate limits are a commercial parameter.** A `429` in production usually means your contracted quota does not match your workload, not that your code is wrong. ## Cost and usage considerations Each route calculation is one billable transaction. There is no bundle across APIs — routing, geocoding, and tiles are three separate meters. Where teams overspend: * **Recomputing routes that did not change.** If neither origin, destination, nor departure time moved, do not call again. * **Calling routing on page load.** A dispatcher opens a screen showing 40 jobs; the frontend fires 40 routing calls. Nobody looks at 38 of them. * **Fetching `instructions` for backend ETA calculations.** Nothing consumes them. * **Building matrices in a loop.** The single largest avoidable cost. See [Matrix Routing](/guides/matrix-routing). See [HERE Pricing Explained](/getting-started/here-pricing-explained) for how call volume and asset-based models differ, and which one your workload wants. ## Common mistakes **Treating `200` as success.** Check `routes.length` and the `notice` array. **Omitting `return`.** You get a default response shape. It may change. Be explicit. **Reversing coordinate order.** `lat,lng`. GeoJSON is `lng,lat`. These are different. **Prototyping on `car`, shipping `truck`.** Different engine, different results, different failure modes. Validate against the mode you will deploy. **Retrying a `403`.** Patient, well-engineered, permanently futile. **Unencoded waypoint attributes.** `nameHint=Fish & Chips! St. 25` produces an ambiguous request. **Assuming beta modes are production-ready.** `bicycle`, `bus`, and `privateBus` carry beta status with limited functionality. ## Best practices * Set `return` explicitly on every call * Check `routes.length` before reading `routes[0]` * Separate geometry caching from ETA caching * Retry `5xx` with backoff and jitter; never retry `403` * Log the endpoint alongside the status code * Validate the transport mode you will ship, not the one that was convenient to test * Encode waypoint attribute values ## API reference Complete parameter tables, response schemas, and enum values are maintained by HERE: * [Routing API v8 developer guide](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/get-started.html) * [Routing API v8 reference](https://docs.here.com/routing/reference/routing-api-v8-calculateroutes) * [Transport modes](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/topics/transport-modes.html) We do not duplicate them. ## Related guides Physical constraints, and why omitting them produces a route no truck can drive. When one call replaces ten thousand. Charge state and consumption as routing inputs. Routing, matrix, and tour planning are not interchangeable. Also: [Authentication](/getting-started/authentication) · [HERE Pricing Explained](/getting-started/here-pricing-explained) · [Maps](/guides/maps) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial solutions. [Talk to us](https://placematic.com/contact/). # HERE Toll Cost API — Implementation Guide Source: https://docs.placematic.com/guides/toll-cost Calculate per-route toll charges for commercial vehicles using the HERE Toll Cost extension, including axle count, vehicle class, and transponder handling. # Toll Cost **A toll figure is not a property of a road. It is a property of a road and a vehicle together.** Change the axle count and the number changes. Change the emission class and, in parts of Europe, it changes again. **The failure mode is not a missing number. It is a plausible number for a vehicle you did not describe** — one that returns `200`, passes review, gets quoted to a customer, and is discovered at settlement. Everything below is elaboration on those two sentences. ## Short verdict **Use HERE Routing v8 with `return=tolls` when you need toll cost for a described commercial vehicle.** It supports `car`, `truck`, `taxi` and `bus`, and the vehicle profile you supply drives the price. **Google's large vehicle routing documentation lists truck toll prices among its unsupported features.** If your dispatch process needs a toll figure for a commercial vehicle, that is a capability you have to source elsewhere. ## Requesting toll cost on HERE Toll cost is not a separate endpoint. It is a `return` value on `/v8/routes`. ```bash theme={null} curl -gX GET 'https://router.hereapi.com/v8/routes?'\ 'origin=41.8781,-87.6298&'\ 'destination=39.7684,-86.1581&'\ 'return=summary,tolls&'\ 'transportMode=truck&'\ 'vehicle[height]=410&'\ 'vehicle[width]=255&'\ 'vehicle[length]=1600&'\ 'vehicle[grossWeight]=36000&'\ 'vehicle[axleCount]=5&'\ 'currency=USD&'\ 'departureTime=2026-09-15T07:00:00-05:00&'\ 'apiKey=YOUR_API_KEY' ``` **Supported transport modes for tolls:** `car`, `truck`, `taxi`, `bus`. **`currency`** takes a three-letter code and converts prices from local currency. Without it you get local currency only, which on a multi-country route means several currencies in one response. **`departureTime`** matters more than it looks. Several toll systems price by time of passage, and the time is derived from the ETA — so a route computed with `departureTime=any` cannot price a time-of-day toll correctly. Verified August 2026 against HERE's [toll cost for a route](https://docs.here.com/routing/docs/routing-v8-tolls-for-route) documentation. Field names and supported modes change; check the current reference before implementing. ### From a GPS trace If you are starting from telematics data rather than an origin-destination pair, the route import service does not return tolls directly. It takes two calls: 1. `POST /v8/import` with `return=routeHandle` and your trace points, which returns a `routeHandle` 2. `GET /v8/routes/{routeHandle}` with `return=tolls` This is the path most ELD and telematics platforms actually need, because they are pricing routes that were already driven rather than planning ones that have not been. See [ELD Platform](/use-cases/eld-platform). ## What comes back Tolls are returned per section, not per route. A route with three sections returns three toll arrays, and the trip total is your sum. ```json theme={null} { "sections": [ { "id": "700c42a0-a981-4817-a250-74037c7699ba", "transport": { "mode": "truck" }, "tolls": [ { "countryCode": "USA", "tollSystemRef": 0, "tollSystem": "TOLL SYSTEM NAME", "fares": [ { "id": "f37b1c4f-cbde-4ef7-9067-e4da106fd0a4", "name": "TOLL SYSTEM NAME", "price": { "type": "value", "currency": "USD", "value": 55.00 } } ] } ] } ] } ``` Each toll carries a `tollSystem` name, a country code, and one or more `fares`. Each fare carries a `price` with a `currency` and a `value`, and where the system supports it, the payment method that produces that price. **Sum the sections. Do not read `tolls[0]`.** A route crossing two toll authorities returns two toll objects. Reading the first one and calling it the trip cost produces a number that is internally consistent, roughly half right, and wrong in the same direction every single time. ## The vehicle profile determines the number This is the part that gets skipped, and it is the whole point of the endpoint. Send `transportMode=truck` with no vehicle dimensions and HERE will price the route. It will return a value, HTTP `200`, and no warning. That value reflects a vehicle you did not describe. On a single Illinois lane, tested with the same origin, destination and departure time: | Request | Toll returned | | ---------------------------------------------------------------------------------- | ------------- | | `transportMode=truck`, no vehicle profile | **\$20.70** | | `transportMode=truck`, full profile — height, width, length, gross weight, 5 axles | **\$55.00** | **A 166% difference on one lane, with no error, no notice, and no indication that the first number describes a different vehicle.** At 500 runs a year — roughly two a working day for a single tractor on a fixed lane — that gap is about \$17,000 of unbilled toll on one truck. Assumptions stated so you can substitute your own: one lane, one vehicle configuration, 500 annual runs, no toll pass discount applied. Run your own lane at [Truck Route Comparison](https://placematic.com/tools/truck-route-comparison/) rather than adopting ours. The competitive context for this figure — what Google returns for a large vehicle, and why that matters at quoting time — is covered separately: [Google's truck routing doesn't price the trip](https://placematic.com/google-truck-routing-toll-cost/). **Parameters that change the price**, beyond the physical profile: * `vehicle[axleCount]` — the primary driver of commercial toll class in the United States * `vehicle[grossWeight]` and dimensions — weight and height bands in most European systems * `vehicle[hovOccupancy]` — occupancy-based pricing where it exists * `tolls[emissionType]` — emission class, and in Germany the CO2 class ### Emission class has a silent default HERE accepts `tolls[emissionType]` in the form `EmissionType[;co2class=N]`, with emission values `euro1` through `euro6` plus `euroEev`, and `co2class` values `1` through `5`. **When `co2class` is not specified, it defaults to `1`.** HERE's documentation also notes that an invalid combination of emission type and CO2 class may produce unexpected tolls. For German operations this is a real money difference and the default is not neutral. Set it explicitly from your fleet register; do not let it fall through. ## Billing Toll data is not a free rider on a route you were computing anyway. HERE documents that requesting toll cost information for a route **counts as an additional transaction**. Budget it as its own line, and cache it — toll prices for a fixed lane and vehicle configuration are stable for far longer than the ETA on the same route. Cache toll cost keyed on lane plus vehicle configuration plus toll pass set, not on the full route request. Two dispatches of the same tractor on the same lane an hour apart have different ETAs and identical tolls. For a fleet running fixed lanes, this is usually the difference between toll cost being a rounding error on the bill and being a line item someone asks about. ## What toll cost is not **HERE documents toll prices as provided on a best-effort basis**, because toll systems are numerous, locally administered, and change without a versioned API. This is a planning and quoting figure. It is not a settlement figure, and HERE does not represent it as one. If your product reconciles against actual toll invoices, the API number is the expectation, not the truth, and your reconciliation logic needs to survive a discrepancy without treating it as a defect. It is also not a routing preference. Getting the toll cost of a route and getting the cheapest route are different operations. `return=tolls` prices the route the router chose. If you want the router to trade distance against toll, that is a cost-optimization problem — see [Choosing Routing vs Matrix](/architecture/choosing-routing-vs-matrix) and evaluate whether the saving justifies the additional calls. And dynamic pricing is out of scope. HERE returns no toll information for HOT lanes, because the price is dynamic. ## Common mistakes **Requesting tolls without describing the vehicle.** Returns `200` and a number for a vehicle you did not specify. On the lane above, that number was 166% low. **Reading `tolls[0]` instead of summing sections.** Consistently wrong on any multi-jurisdiction route, and never wrong enough to look broken. **Leaving `co2class` unset in Germany.** It defaults to `1`. That is a price, not an absence of one. **Using `departureTime=any` on a time-priced toll system.** No ETA means no time of passage means no correct price. **Treating the API figure as a settlement number.** Best-effort by documentation. Build reconciliation that tolerates a delta. **Not caching.** Tolls are stable where ETAs are not, and the request is billed as an additional transaction. **Assuming a currency.** Without `currency`, a cross-border route returns several local currencies in one response, and summing them is a type error your language will happily let you commit. **Planning a commercial vehicle dispatch on a platform that does not price commercial tolls.** Google's large vehicle routing documentation lists truck toll prices as unsupported. Confirm the capability exists before the toll figure becomes load-bearing in a quoting workflow. ## How to verify on your own lanes **One.** Pick five lanes your fleet actually runs, with real toll invoices for them. **Two.** Request each lane twice on HERE — once with `transportMode=truck` and no vehicle profile, once with your full profile including `axleCount`. Record both numbers. The gap between them is the size of the error you would ship by omitting the profile. **Three.** Compare the full-profile figure against the invoice. You are testing whether best-effort is close enough for your quoting margin, not whether it is exact. **Four.** Repeat with a toll pass specified and without one. In several US systems the pass price and the cash price differ enough to matter at fleet scale. **Five.** Put the profiled figure next to whatever your current dispatch process assumes. If your quoting spreadsheet carries a flat per-mile toll allowance, that comparison is usually the shortest route to a decision. ## Related documentation Constraints, units, and the trap geometry that proves they apply. Google Large Vehicle Routing versus HERE truck routing, constraint by constraint. Pricing routes that were driven, not planned. Route handles and traces. What to cache, and why toll cost caches better than anything else here. Also: [Fleet Routing](/use-cases/fleet-routing) · [Hazmat Routing](/use-cases/hazmat-routing) · [Reducing Google Maps Costs](/use-cases/reducing-google-maps-costs) ## Sources **HERE** * [Get toll cost information for route](https://docs.here.com/routing/docs/routing-v8-tolls-for-route) — `return=tolls`, supported modes, currency conversion, best-effort statement * [Get toll cost with route import service](https://docs.here.com/routing/docs/routing-v8-tolls-routeimport) — route handle flow for GPS traces * [Get toll cost for multi-section route](https://docs.here.com/routing/docs/routing-v8-tolls-multileg) — per-section toll arrays * [Toll calculation with emission type and CO2 class](https://www.here.com/learn/blog/toll-emission-co2-germany) — `tolls[emissionType]`, `co2class` default * [Routing API v8 developer guide](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/get-started.html) **Google** * [Large vehicle routing overview](https://developers.google.com/maps/documentation/routes/lvr) — truck toll prices listed among unsupported features **Placematic** * [Truck Route Comparison](https://placematic.com/tools/truck-route-comparison/) — run the profiled and unprofiled request on your own lane *HERE toll documentation verified August 2026. Google large vehicle routing limitations verified August 2026. The Illinois lane figures are Placematic's own measurement on a single lane and vehicle configuration, stated with assumptions above; they are illustrative of the mechanism, not a benchmark. Toll systems, coverage and pricing change; verify against primary sources.* *** Need to know what your lanes actually cost? Placematic can run profiled and unprofiled toll calculations across your real lane set and reconcile the output against your invoices. Placematic is an official HERE Technologies reseller and implementation partner. [Cost Reduction Audit](https://placematic.com/here-location-services/cost-reduction-audit/). # HERE Tour Planning — Implementation Guide Source: https://docs.placematic.com/guides/tour-planning Implementing HERE Tour Planning v3 — the vehicle routing problem, plan and fleet structure, async solving, unassigned jobs, and why looping the routing API is not optimization. # HERE Tour Planning Tour Planning is a solver, not a router. You hand it a problem — jobs, vehicles, constraints — and it returns a sequence. This is the API behind both `/here-tour-planning/` and `/route-optimization/` on placematic.com. They describe one HERE product. There is one guide. ## What problem this guide solves You have 200 stops, 12 vehicles, delivery time windows, vehicle capacities, driver shifts, and two refrigerated trucks that must handle the frozen goods. You need to know which vehicle visits which stop, in what order. This is the Vehicle Routing Problem. It is NP-hard. You are not going to solve it with a loop and a nearest-neighbour heuristic, and every hour you spend trying is an hour you could have spent shipping. ## When to use Tour Planning HERE's solver handles these VRP variants: * **Capacitated VRP** — vehicle capacity constrains what can be loaded * **VRP with time windows** — stops accept service only within defined intervals * **Multi-depot VRP** — vehicles start from different locations * **Heterogeneous fleet VRP** — mixed vehicle types with different costs, capacities, and capabilities * **Pickup and delivery VRP** — collect at A, deliver at B, in one tour * **VRP with priorities** — high-priority jobs are protected from being dropped when capacity binds * **VRP with reloads** — a vehicle returns to a depot mid-tour to reload If your problem is on that list, use Tour Planning. ## When NOT to use it Tour Planning is not a routing API. It returns a sequence and an itinerary. If you need turn-by-turn geometry for a single leg, that is [Routing](/guides/routing). * **Point-to-point navigation.** Use [Routing](/guides/routing). * **A table of travel times.** Use [Matrix Routing](/guides/matrix-routing). Tour Planning consumes that kind of cost internally; do not ask it for the table. * **Real-time dispatch on every event.** Solving takes seconds to minutes. It is not a per-request operation in a hot path. * **Two or three stops.** A solver is overhead. Sort them. * **Problems where you need to see and tune the objective function.** Tour Planning exposes cost weights, not a general-purpose optimizer. If you need custom objectives beyond distance, time, and fixed cost, you may want your own solver fed by [Matrix Routing](/guides/matrix-routing). ## Structure of a problem Two halves. Get this mental model right and the rest follows. **`plan`** — the jobs. Each job has an id, tasks (`deliveries`, `pickups`), places with locations, service `duration`, time windows, and `demand`. **`fleet`** — the vehicles. `types` defines vehicle classes with costs, capacity, shifts, and limits. `profiles` defines routing behaviour — transport type, departure time, traffic handling. `fleet.types[].profile` is a **string reference** into `fleet.profiles[].name`. It is not a transport mode. Writing `"profile": "truck"` when no profile is *named* `truck` is a common and confusing failure. Name the profile, then reference it. ## Submitting a problem ```bash cURL (sync) theme={null} curl --location 'https://tourplanning.hereapi.com/v3/problems' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ' \ --data '{ "plan": { "jobs": [{ "id": "myJob", "tasks": { "deliveries": [{ "places": [{ "location": {"lat": 52.46642, "lng": 13.28124}, "times": [["2020-07-04T10:00:00.000Z","2020-07-04T12:00:00.000Z"]], "duration": 180 }], "demand": [1] }] } }] }, "fleet": { "types": [{ "id": "myVehicleType", "profile": "normal_car", "costs": {"distance": 0.0002, "time": 0.005, "fixed": 22}, "shifts": [{ "start": {"time": "2020-07-04T09:00:00Z", "location": {"lat": 52.52568, "lng": 13.45345}}, "end": {"time": "2020-07-04T18:00:00Z", "location": {"lat": 52.52568, "lng": 13.45345}} }], "limits": {"maxDistance": 300000, "shiftTime": 28800}, "capacity": [10], "amount": 1 }], "profiles": [{"name": "normal_car", "type": "car", "departureTime": "2020-07-04T09:15:00Z"}] }, "configuration": { "termination": {"maxTime": 2, "stagnationTime": 1} } }' ``` ```bash cURL (async) theme={null} curl --location 'https://tourplanning.hereapi.com/v3/problems/async' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ' \ --data '{ ...same body as sync... }' # Returns: # { "statusId": "", "href": "https://tourplanning.hereapi.com/v3/status/" } ``` API key authentication works too: drop the `Authorization` header and append `?apikey=` to the URL. This applies to all Tour Planning endpoints. See [Authentication](/getting-started/authentication). ## The async lifecycle For anything of real size, use `/v3/problems/async`. 1. `POST` the problem → receive `statusId` and `href` 2. `GET` the `href` — poll while status is `pending` or `inProgress` 3. On `success`, the response contains `resource.href` pointing to the solution 4. `GET` the solution at `/v3/problems/{problemId}/solution` Terminal statuses are `success`, `timeout`, and `failure`. A `timeout` is not a bug — see below. Use the `href` HERE returns. Do not construct status or solution URLs yourself. ## Key concepts **`configuration.termination` controls solve quality.** `maxTime` caps wall-clock seconds. `stagnationTime` stops when the solution stops improving. **These are the two most important parameters in the request, and both are routinely left at example values.** A two-second `maxTime` on a 200-stop problem produces a legal solution that a dispatcher will look at and reject. Increase it. Measure the marginal improvement. Find your knee. **`timeout` status means the solver did not converge within `maxTime`.** It is a tuning signal, not an error. Raise `maxTime` or reduce problem size. **Jobs can be dropped.** If capacity, shift time, or time windows make full service impossible, the solver returns unassigned jobs with reasons. **Your integration must handle a solution that does not serve every stop.** A dispatcher UI that silently omits them is worse than useless. **Priorities protect jobs from being dropped.** High-priority jobs are preferentially retained when constraints bind. **Costs shape the objective.** `distance`, `time`, and `fixed` per vehicle type. Setting `fixed` high discourages using an extra vehicle; setting it to zero encourages spreading load. This is where you encode business policy, and most teams never touch it. **Truck profiles are supported.** `type: "truck"` with vehicle parameters, same physical constraints as [Truck Routing](/guides/truck-routing). Optimizing a truck fleet on car profiles produces sequences that are infeasible in exactly the way a car route is infeasible for a truck. **Traffic handling is a profile property.** Live or historic traffic materially changes ETAs and therefore the optimal sequence. A 9am tour and a 2pm tour are different problems. ## Production architecture **Solve is a job, not a request.** Submit async, persist `statusId`, poll on a worker, return control to the caller immediately. Blocking a dispatcher's HTTP request on a 90-second solve reproduces every problem async exists to prevent. **Persist `statusId` before you poll.** Process restarts. Resubmitting a solved problem bills again and returns a different sequence, which confuses everyone. **Solve on a schedule, not on every change.** Nightly, or on dispatch-window close. Re-solving on every order that arrives produces route churn drivers cannot follow. **Model unassigned jobs as a first-class outcome.** Surface them. Explain why. Let a human decide. **Store the solution, not just the sequence.** When a driver deviates, you need to know what the plan was. **Replanning mid-day is a new problem.** Vehicle current location becomes the shift start. Completed jobs are removed. This is a fresh submission, not an incremental update. **Truck parameters belong on the vehicle record**, not in the request builder. Same argument as [Truck Routing](/guides/truck-routing). ## Cost and usage considerations Tour Planning is included in the HERE Base Plan. This is worth knowing before anyone builds an approximation to avoid a licensing conversation that does not exist. Confirm your entitlement anyway — Base Plan inclusion and *your contract's* inclusion are separate facts. See [Getting a HERE API Key](/getting-started/getting-a-here-api-key). Where teams overspend, and it is rarely on Tour Planning itself: * **Approximating tour planning with N routing calls.** Slower, more expensive, and the routes are worse. This is the single largest waste in fleet software. * **Re-solving unchanged problems.** Hash the problem. If nothing changed, reuse the solution. * **Solving per-vehicle instead of per-fleet.** The whole point is fleet-wide balancing. * **`maxTime` set so high that solves cost more compute than the improvement is worth.** Measure the knee. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Common mistakes **Building an optimizer out of the Routing API.** The reason this page exists. **Setting `profile` to a transport mode instead of a profile name.** It is a reference into `fleet.profiles`. **Leaving `maxTime: 2` from the documentation example.** Legal solution, unusable routes. **Treating `timeout` as a failure.** It means "raise `maxTime`." **Ignoring unassigned jobs.** The solver told you it could not serve them. Read the response. **Blocking an HTTP request on a synchronous solve.** Use `/async`. **Constructing the status or solution URL.** Use the returned `href`. **Optimizing a truck fleet on car profiles.** Infeasible sequences, confidently returned. **Leaving `costs` at example values.** `fixed` is how you tell the solver whether adding a vehicle is cheap or expensive. That is a business decision, not a default. **Re-solving on every inbound order.** Route churn. Drivers stop trusting the app. ## Best practices * Use `/v3/problems/async` for anything beyond a toy * Persist `statusId` before polling; poll on a worker * Tune `maxTime` and `stagnationTime` against your own problem size; find the knee empirically * Set `costs` to reflect real vehicle economics * Handle and surface unassigned jobs explicitly * Name profiles clearly and reference them correctly * Use truck profiles and vehicle constraints for truck fleets * Hash the problem; skip re-solves that would produce the same answer * Solve on a schedule; treat mid-day replanning as a new problem ## API reference * [HERE Tour Planning API introduction](https://docs.here.com/tour-planning/docs/introduction-tour-planning) * [Quick start](https://docs.here.com/tour-planning/docs/quick-start) * [Developer guide](https://docs.here.com/tour-planning/docs/introduction-tour-planning) Complete problem and solution schemas, constraint definitions, and objective configuration are maintained by HERE. We do not duplicate them. ## Related guides The cost table underneath any optimization — including your own solver. Vehicle constraints, which Tour Planning profiles also accept. Turn-by-turn geometry for the legs Tour Planning sequences. Routing, matrix, and solver — the distinction that costs the most to get wrong. Also: [EV Routing](/guides/ev-routing) · [Authentication](/getting-started/authentication) · [HERE Tour Planning](https://placematic.com/here-location-services/here-tour-planning/) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial solutions. [Talk to us](https://placematic.com/contact/). # HERE Truck Routing — Implementation Guide Source: https://docs.placematic.com/guides/truck-routing Implementing HERE truck routing in production — physical constraints, hazmat classes, trap geometry validation, why a valid response can still be an undrivable route, and how the constraint set compares to Google Large Vehicle Routing. # HERE Truck Routing Setting `transportMode=truck` selects the truck routing engine. It does not tell HERE how tall your vehicle is. Omit the dimensions and you get a route. It returns `200`. It contains a polyline. It may also route a 4.1-metre trailer under a 3.4-metre bridge, and HERE will not warn you, because you did not tell it there was a trailer. This is the single most consequential detail on this site. ## What problem this guide solves Car routing optimizes for time. Truck routing optimizes for time *subject to physical and legal feasibility*. Bridge clearances. Weight limits. Axle restrictions. Hazmat prohibitions. Turn radii. Roads where commercial vehicles are banned outright. If your product dispatches commercial vehicles, an infeasible route is not a UX defect. It is a bridge strike, a citation, or a driver reversing a 53-foot trailer down a residential street. ## When to use truck routing * Any vehicle above light-passenger class: trucks, vans over 3.5t, tractor-trailers * Hazardous materials transport * Oversized or overweight loads * ELD and fleet platforms where the route drives compliance * Any case where the map's truck attributes — weight limits, height restrictions, preferred truck routes — must be respected ## When NOT to use it * **Passenger vehicles.** Truck routing on a sedan produces longer, slower routes for no benefit. * **Light commercial vehicles that legally route as cars.** HERE exposes a `category` parameter with a `lightTruck` value, which exempts the vehicle from many legal truck restrictions while still applying physical dimension limits. Check whether your fleet qualifies before paying the routing penalty. * **Multi-stop optimization.** Truck routing gives you A→B. Ordering the stops is Tour Planning. * **Distance matrices.** [Matrix Routing](/guides/matrix-routing) supports truck mode. Do not loop. ## The constrained request ```bash cURL theme={null} curl -gX GET 'https://router.hereapi.com/v8/routes?'\ 'transportMode=truck&'\ 'origin=42.0641,-88.0509&'\ 'destination=41.5250,-88.0817&'\ 'vehicle[height]=410&'\ 'vehicle[grossWeight]=35000&'\ 'vehicle[axleCount]=5&'\ 'return=summary,polyline&'\ 'apiKey=YOUR_API_KEY' ``` ```python Python theme={null} import os, requests def truck_route(origin: str, destination: str) -> dict: resp = requests.get( "https://router.hereapi.com/v8/routes", params={ "transportMode": "truck", "origin": origin, "destination": destination, "vehicle[height]": 410, # centimetres "vehicle[grossWeight]": 35000, # kilograms "vehicle[axleCount]": 5, "return": "summary,polyline", "apiKey": os.environ["HERE_API_KEY"], }, timeout=15, ) resp.raise_for_status() body = resp.json() if not body.get("routes"): raise RuntimeError(f"No route: {body.get('notice')}") return body["routes"][0] ``` **Units are not obvious and are not validated for plausibility.** `height` is centimetres. `grossWeight` is kilograms. Pass `4.1` for a 4.1-metre vehicle and HERE will route a four-centimetre truck through everything. The response will be `200`. Confirm every unit against the [Routing API v8 reference](https://docs.here.com/routing/reference/routing-api-v8-calculateroutes) before you ship. This is not a place to guess. ## Key concepts **`grossWeight` replaced `weightLimit`.** The older `weightLimit` parameter is deprecated in the current v8 specification. If you are reading a 2021 tutorial, you are reading a deprecated parameter. **`axleCount` counts the whole combination.** Base vehicle plus every attached trailer. Valid range is 2–255. Supported in `truck`, `bus`, and `privateBus`; in `car` and `taxi` it carries beta status. **Hazmat is two parameters, not one, and they govern different things.** `shippedHazardousGoods` is a list of cargo types — `explosive`, `gas`, `flammable`, `combustible`, `organic`, `poison`, `radioactive`, `corrosive`, `poisonousInhalation`, `harmfulToWater`, `other`. `tunnelCategory` is the ADR tunnel restriction code, with values `B`, `C`, `D`, `E`. Confusing them is the most consequential hazmat integration error. See [How does HERE handle hazmat routing?](/faq/how-does-here-handle-hazmat). HERE exposes `shippedHazardousGoods` with class values including `B`, `C`, `D`, and `E`. Which classes are supported depends on transport mode. Do not map your internal hazmat flags without reading the reference. **`category=lightTruck` is a legal exemption, not a physical one.** It exempts the vehicle from many restrictions written for normal trucks. Restrictions on physical dimensions and cargo still apply. Setting it does not let you skip `height` and `grossWeight`. **Constraints you do not send are constraints HERE cannot apply.** There is no partial mode. There is no warning. The engine assumes an unconstrained vehicle for anything you omit. ## Validating that constraints are actually applied This is the part most teams skip, and the reason bridge strikes make the news. Route a vehicle with a height of 410 cm through these locations. If any returns a path, your constraints are not reaching the engine: | Location | Constraint | | --------------------------------------- | ---------------------------------------------- | | 11foot8 bridge, Durham, NC | Clearance well below 4.1 m | | Storrow Drive, Boston, MA | Commercial vehicles prohibited; low overpasses | | Southern State Parkway, Long Island, NY | Commercial vehicles prohibited | Wire these three into your CI suite as assertions, not as a manual QA step. A refactor that drops `vehicle[height]` from the params dict is invisible in code review and obvious in a failing test. Run the same three with `transportMode=car`. Every one should return a route. That is your control — it proves the test is exercising the constraint and not merely failing for an unrelated reason. ## Production architecture **Vehicle profiles belong in your data model, not in call sites.** Height, weight, axle count, hazmat class, and category are attributes of a vehicle. Store them once. Pass them everywhere. Do not let a developer hardcode `height=410` in a new endpoint. **Validate profiles at write time.** A vehicle record with a null height should fail validation before it ever reaches a routing call. **Truck routing is slower than car routing.** More constraints, more search space. Size your timeouts and your UI accordingly. **Compliance and routing are coupled.** For ELD platforms, the route determines hours-of-service feasibility. If routing changes, HOS projections change. Model that dependency explicitly rather than discovering it in an audit. ## Cost and usage considerations Truck routing bills as a routing transaction — the same rate and the same free allowance as a car route. Google's large vehicle routing requires the traffic-aware optimal routing preference, which bills above its Essentials rate, so on Google the vehicle profile moves you to a different meter and on HERE it does not. Toll cost is billed separately on HERE and counts as an additional transaction. Google's large vehicle routing documentation lists truck toll prices as unsupported. See [Toll Cost](/guides/toll-cost). Truck routing inclusion in HERE's free tier should be confirmed against your specific account before you scope a pilot around it. Free-tier product coverage varies and should not be inferred from general documentation. Placematic can confirm your entitlement before you commit engineering time. For matrices of truck travel times, [Matrix Routing](/guides/matrix-routing) accepts truck mode and vehicle parameters. One call, not N. ## Common mistakes **Setting `transportMode=truck` and stopping there.** The engine is selected. The vehicle is not described. **Passing metres instead of centimetres.** A four-centimetre truck routes anywhere. **Using the deprecated `weightLimit`.** Use `grossWeight`. **Counting only the tractor's axles.** `axleCount` includes trailers. **Mapping an internal `is_hazmat` boolean onto HERE's classed enum.** Read the class definitions. **Assuming `lightTruck` waives dimension limits.** It waives legal restrictions. Bridges do not care about your category. **Validating on synthetic coordinates.** Two points in a parking lot will route under any constraint set. Test on geometry designed to reject you. **Shipping without regression tests on trap locations.** The constraint that silently disappeared in a refactor is the one that ends up in an incident report. ## Best practices * Store vehicle dimensions as first-class model attributes * Assert trap-geometry rejection in CI, with a `car`-mode control * Confirm every unit against HERE's reference before first deploy * Fail loudly on a routing response with empty `routes` * Confirm free-tier and contract entitlement for truck mode before scoping * Use [Matrix Routing](/guides/matrix-routing) for many-to-many, with truck parameters ## API reference * [Routing API v8 reference](https://docs.here.com/routing/reference/routing-api-v8-calculateroutes) — full truck parameter tables and enums * [Transport modes](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/topics/transport-modes.html) * Truck categories are documented within the Routing v8 developer guide ## Related guides The base engine, request shaping, and error semantics. Why the toll figure depends on the vehicle profile, and what Google returns instead. Truck travel times across many origins and destinations, in one call. Constraint scope, coverage limits, and where the gap actually sits. Asset-based versus call-volume pricing for fleet workloads. Also: [Choosing the Right HERE APIs](/getting-started/choosing-the-right-here-apis) · [Authentication](/getting-started/authentication) · [HERE Location Services](https://placematic.com/here-location-services/) *** Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner helping companies choose the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial solutions. We have deployed HERE truck routing into production ELD systems. [Talk to us](https://placematic.com/contact/). # Placematic Developer Hub Source: https://docs.placematic.com/index Implementation guidance for HERE Location Services — choosing APIs, forecasting costs, migrating from Google Maps, and shipping routing systems that survive production. # HERE Location Services, implemented Placematic is an official HERE reseller and Gold Partner. We issue production API keys, hold the contract, and write the guidance below from systems we have shipped since 2016. Start with a request that returns `200`. ```bash cURL theme={null} curl "https://router.hereapi.com/v8/routes" -G \ --data-urlencode "apiKey=$HERE_API_KEY" \ --data-urlencode "transportMode=truck" \ --data-urlencode "origin=42.0641,-88.0509" \ --data-urlencode "destination=41.5250,-88.0817" \ --data-urlencode "vehicle[height]=410" \ --data-urlencode "vehicle[grossWeight]=35000" \ --data-urlencode "return=summary" ``` ```python Python theme={null} import os, requests resp = requests.get( "https://router.hereapi.com/v8/routes", params={ "apiKey": os.environ["HERE_API_KEY"], "transportMode": "truck", "origin": "42.0641,-88.0509", "destination": "41.5250,-88.0817", "vehicle[height]": 410, "vehicle[grossWeight]": 35000, "return": "summary", }, timeout=10, ) resp.raise_for_status() print(resp.json()["routes"][0]["sections"][0]["summary"]) ``` ```javascript Node.js theme={null} const params = new URLSearchParams({ apiKey: process.env.HERE_API_KEY, transportMode: "truck", origin: "42.0641,-88.0509", destination: "41.5250,-88.0817", "vehicle[height]": "410", "vehicle[grossWeight]": "35000", return: "summary", }); const resp = await fetch( `https://router.hereapi.com/v8/routes?${params}` ); if (!resp.ok) throw new Error(`HERE returned ${resp.status}`); console.log(await resp.json()); ``` No key yet? [Request one](https://placematic.com/api-key/) — issued within one business day, no credit card, no demo call. Placematic USA LLC is the contracting party. You never open a HERE account. ## Start here Platform overview, API keys, authentication, and how HERE pricing actually works. Truck routing, distance matrices, batch geocoding, and migrating off Google Maps. Reference architectures for fleet and ELD platforms, delivery zones, and retail networks. HERE against Google, TomTom, and Mapbox — including where they beat HERE. Learn how batching, caching, pre-computation and choosing the right HERE API can reduce location API costs. Learn about a Model Context Protocol endpoint that exposes the Placematic Spatial API to AI agents as callable tools. ## What this documentation covers HERE maintains the parameter tables and response schemas. We link to them. What we write is the layer around the reference: **Which API answers your question.** Routing, Distance Matrix, and Tour Planning look interchangeable and are not. Building a matrix from a loop of routing calls is the most expensive architectural mistake in this domain. **What it will cost.** Per-1,000 rates are close across vendors. Your bill is set by call mix, volume tier, and whether you batched. We show you how to forecast it before you sign anything. **How teams get it wrong.** `transportMode=truck` selects a routing engine. It does not supply your vehicle's dimensions. Omit them and HERE returns a route, without warning, that a 4.1m trailer cannot physically drive. **When to use something else.** For consumer place discovery, business hours, and reviews, Google's data is better and you should keep it. Under roughly 10k calls a month, migration is a rounding error you will spend a quarter chasing. ## Featured guides Physical constraints as first-class inputs — and the trap geometry that proves your integration applies them. Call volume versus asset-based pricing, and how to model a bill you can defend to finance. A decision path by the question your system asks, not by product name. Endpoint mapping, sequencing, and validating route quality during cutover. ## Who builds on this Placematic has been a HERE Gold Partner since 2016, deploying HERE into production routing engines, ELD platforms, and field operations tools. Every page here comes from an integration we shipped, including the ones that went badly the first time. See how teams run this in production: [Placematic case studies](https://placematic.com/case-studies/) ## Our implementation philosophy A production location system should not call an external API for every decision. Use HERE where fresh routing, traffic, geocoding or map data matters. Cache, batch and materialize everything else. ## Need production keys? Placematic helps engineering teams select the right HERE APIs, forecast usage, migrate from Google Maps, and reach production. One contract, one invoice, no direct HERE account. One business day. No credit card. No demo call required. # Authentication Source: https://docs.placematic.com/mcp/authentication How the Placematic MCP Server authenticates a caller, and how to handle the token. The server accepts an access token issued by Placematic. There are two ways to present it. Which one you use is decided by your client, not by preference. ## Header — preferred ``` Authorization: Bearer ``` Works with Cursor, Claude Code, MCP Inspector and any custom agent. The token stays in a configuration file or an environment variable and never appears in a URL, a browser history or a request log. Use this wherever your client supports it. ## Connector URL — for clients that cannot send headers ``` https://mcp.placematic.io/k//mcp ``` The custom connector dialogs in both Claude and ChatGPT accept a URL and optional OAuth credentials. Neither has a field for a header. For those clients, the token is part of the path. A connector URL **is** a credential. It travels with every screenshot, every "here's the link" in Slack, and every support ticket. Treat it exactly as you would treat a password, and request a separate token for connector use rather than reusing the one in your backend. This is a beta accommodation. OAuth 2.1 is the intended replacement and is on the roadmap. ## Discovery paths The server returns `404` on `/.well-known/oauth-protected-resource`, `/.well-known/oauth-authorization-server` and `/register`. This is deliberate: it tells a client there is no OAuth here and that the credential it already has is sufficient. A client that receives `401` on those paths instead will start an authorization flow and fail. You do not need to configure anything for this. It is documented because it explains why a connector that appears to be reaching the server can still report a connection failure — see [Troubleshooting](/mcp/troubleshooting). ## Failure responses | Status | Body `code` | Cause | | ------ | -------------- | ------------------------------------------------------- | | `401` | `unauthorized` | Token missing, malformed, or not recognised | | `403` | `forbidden` | Token recognised but not permitted for this channel | | `403` | — | Request arrived on a hostname the server does not serve | The `401` response is identical for a missing token and an unrecognised one. This is intentional and not a bug — the response does not confirm whether a given token exists. ## Handling the token **Do not put it in a repository.** Use an environment variable in client configuration files. `.cursor/mcp.json` in particular is committed by accident more often than any other file in this list. **Do not send it over email or chat.** Placematic issues tokens as a one-time secure link. **Rotate on exposure.** If a token appears in a screenshot, a shared terminal session, or a support thread, ask for a new one. Rotation is immediate and free; the old token stops working the moment the new one is issued. **Use separate tokens for separate purposes.** A token in a connector URL and a token in a backend service should not be the same token, so that revoking one does not interrupt the other. ## Scope Every tool is a read operation. There is no write, update or delete capability in the schema, so a compromised token exposes query access to licensed map data and to your own territory configuration — it cannot change anything. Per-key rate limits and request logging are not implemented during beta. If usage attribution is a requirement for you, say so when you request access. # Placematic MCP Server Source: https://docs.placematic.com/mcp/overview A Model Context Protocol endpoint that exposes the Placematic Spatial API to AI agents as callable tools. The Placematic MCP Server lets an AI agent call the Placematic Spatial API directly. It is a thin layer: each tool maps to one endpoint you could call yourself over REST, with your own credential, and returns what that endpoint returns. **Beta.** The MCP Server is in beta and is excluded from the availability commitments in the [Placematic SLA](https://placematic.com/sla/). The Placematic API services it calls remain covered. ## Endpoint ``` https://mcp.placematic.io/mcp ``` Transport is Streamable HTTP. The server targets MCP revision `2026-07-28` and remains compatible with clients on `2025-11-25`. Both connect to the same endpoint — you do not select a version. ## What it does Four tools are available today: | Tool | Answers | | ----------------------------- | --------------------------------------------------------------------------------------------------------------- | | `placematic_assign_territory` | Which of your territories covers a point, which of your locations serve it, and on what delivery terms | | `placematic_route` | Road distance and driving time, with a truck profile that respects height, weight, length and axle restrictions | | `placematic_geocode` | Turns a complete postal address into coordinates | | `placematic_reverse_geocode` | Turns coordinates into the nearest postal address | Full argument schemas and response shapes: [Tool reference](/mcp/tools). ## What it does not do Read this before you design against it. **No write operations.** Every tool is a query. No write, update or delete operation exists in the schema — the capability is absent, not disabled. **No batch calls.** One tool call answers one question. Batching an agent's work into fewer calls is your concern, not the server's. ## What you need An access token issued by Placematic. Placematic USA LLC is the contracting party — you do not open a HERE account, negotiate a HERE contract, or manage a HERE key. Issued within one business day. No credit card, no demo call. ## Next Connect Cursor, Claude Code, Claude or ChatGPT. Header auth, connector URLs, and how to handle the token. Arguments, response shapes, and error behaviour for all four tools. Every failure mode we hit while building this, and what causes it. # Quickstart Source: https://docs.placematic.com/mcp/quickstart Connect an agent to the Placematic MCP Server and make your first tool call. You need an access token. If you do not have one, [request access](https://placematic.com/mcp-access/) — tokens are issued within one business day. ## Verify the endpoint first Before configuring a client, confirm the server answers for your token. This separates a bad token from a bad client config, which is worth thirty seconds. ```bash theme={null} export MCP_URL=https://mcp.placematic.io/mcp export TOKEN= curl -s -X POST "$MCP_URL" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' \ | sed -n 's/^data: //p' | jq -r '.result.tools[].name' ``` Expected output: ``` placematic_assign_territory placematic_route placematic_geocode placematic_reverse_geocode ``` The `Accept` header must contain **both** `application/json` and `text/event-stream`. Sending only `application/json` returns `Not Acceptable`. The server replies as an SSE stream, which is why the example pipes through `sed` before `jq` — `jq` cannot parse `event: message` directly. ## Connect a client Edit `~/.cursor/mcp.json` for all projects, or `.cursor/mcp.json` inside one project. ```json theme={null} { "mcpServers": { "placematic": { "url": "https://mcp.placematic.io/mcp", "headers": { "Authorization": "Bearer ${env:PLACEMATIC_MCP_TOKEN}" } } } } ``` Set `PLACEMATIC_MCP_TOKEN` in your environment rather than pasting the token into the file — `.cursor/mcp.json` is frequently committed to a repository by accident. Verify under **Cursor Settings → Tools & MCP**. The server should show as connected with four tools listed. ```bash theme={null} claude mcp add --transport http placematic https://mcp.placematic.io/mcp \ --header "Authorization: Bearer $PLACEMATIC_MCP_TOKEN" claude mcp list ``` Claude Code sends the header directly. This is the least fragile way to connect. The custom connector dialog accepts a URL and optional OAuth credentials. There is no field for a header, so the token goes in the URL path. 1. **Settings → Connectors → Add custom connector** 2. Name: `Placematic` 3. Remote MCP server URL: ``` https://mcp.placematic.io/k//mcp ``` 4. Leave OAuth Client ID and Secret empty. 5. **Add**, then **Connect**. 6. Open a **new conversation** and enable `Placematic` in the tools menu. On Team and Enterprise plans, organisation connectors are added by an Owner. If you do not see the option, you are not the one who can add it. Custom MCP connectors require developer mode, which is off by default. 1. **Settings → Apps → Advanced settings → Developer mode → on** 2. **Create custom connector**, URL: ``` https://mcp.placematic.io/k//mcp ``` 3. In a chat: **+ → Developer mode →** select the connector. On Business, Enterprise and Edu plans this is a workspace-level setting. A workspace administrator must enable developer mode and custom MCP connectors under **Workspace Settings → Permissions & Roles → Connected Data** before any member can add a connector. Plan for this in your rollout; it is usually the longest step. Custom connectors are web-only. They do not appear in the ChatGPT desktop or mobile apps. Any MCP client library that supports Streamable HTTP will connect. Point it at `https://mcp.placematic.io/mcp` and send `Authorization: Bearer `. There is no `initialize` handshake required on revision `2026-07-28`; clients on `2025-11-25` that send one are handled on the compatibility path. You do not need to branch on this. ## First call With the connector enabled, ask a question in natural language. Do not name the tool — the point of the tool descriptions is that the model selects correctly without being told. ``` An order just came in from 1139 N Arlington Heights Rd, Itasca IL. Which of our locations delivers there, and on what terms? ``` The agent should chain two calls — `placematic_geocode` to resolve the address, then `placematic_assign_territory` to answer the question — and return the serving location with its delivery conditions. ## After a schema change Clients cache tool schemas and do not refresh them on their own. If Placematic changes a tool's arguments, your agent will keep sending the old ones and receive validation errors until the connector is refreshed. **Cursor and Claude Code:** restart the client. **Claude and ChatGPT connectors:** remove the connector and add it again. Toggling it off and on in a conversation is not enough. Schema changes are announced by email to the address on your access request. # Tool reference Source: https://docs.placematic.com/mcp/tools Arguments, response shapes and error behaviour for every tool the Placematic MCP Server exposes. Four tools are available. Each maps to one Placematic Spatial API endpoint. Arguments below are the names the agent uses — they are not always the parameter names on the underlying REST call. An empty result is not an error. Tools that can legitimately find nothing — no address match, no territory covering a point — return an empty result with `isError: false`. An agent that treats those as failures will retry in a loop and consume transactions for nothing. *** ## `placematic_assign_territory` Determines which of your delivery territories or sales grids cover a point, which of your locations serve it, and on what delivery terms. Answers from your own configuration held in Placematic — not from postal or administrative geography. Longitude in decimal degrees, WGS84. **Note the axis order: `x` is longitude.** In the United States this value is negative. Latitude in decimal degrees, WGS84. The axis naming differs from the other tools in this reference, which take `latitude` and `longitude`. This is the most common source of transposed coordinates when writing against the API directly. **Response** ```json theme={null} { "territories": [ { "id": 48303, "externalId": "TRG_134046", "deliveryActive": true, "deliveryConditions": [{ "orderValue": 0, "deliveryPrice": 3000 }], "posList": [ { "id": 40705, "externalId": "134046", "distance": 12143.36, "priority": 1 } ] } ], "evaluated_at": "2026-08-01T09:14:02Z" } ``` `deliveryPrice` and `orderValue` are integers in cents. `3000` is \$30.00. There is no currency field in the response today; amounts are assumed to be in the currency of your Placematic contract. `distance` is in metres, straight-line from the queried point to the serving location. It is not a driving distance. Use `placematic_route` if driving distance matters. `evaluated_at` is the time the query ran. Territory data is live — if the configuration is edited, the answer changes. There is no versioned snapshot. **Empty result:** a point outside every territory returns an empty `territories` array. This is a valid business answer. *** ## `placematic_route` Road distance, driving time and toll cost between two points, with a truck profile that respects vehicle restrictions in the HERE road network. `car` or `truck`. Use `truck` for any commercial goods vehicle. Vehicle height in **centimetres**. A standard US semi at 13 ft 6 in is `411`. Gross vehicle weight in **kilograms**. 80,000 lb is `36287`. Vehicle length in **centimetres**. A 53 ft trailer combination is about `1615`. Number of axles. A standard US 5-axle semi is `5`. Toll rates are charged by axle — omitting this materially changes the cost estimate. **`transport_mode: "truck"` selects a routing engine. It does not tell that engine what your truck is.** Omit the dimensions and you get a generic truck route, with a toll figure computed for a default profile. Measured on one 310-mile Illinois lane, same origin, same destination: | | No vehicle profile | Height, weight, length, 5 axles | | --------- | ------------------ | ------------------------------- | | Distance | 307.9 mi | 310.0 mi | | Duration | 5 h 7 min | 5 h 7 min | | **Tolls** | **USD 20.70** | **USD 55.00** | Same four toll gantries, same two tollways. The toll is 166% higher with the full profile, because Illinois charges by axle. Distance moved by two miles and duration not at all — **there is no signal in the cheaper answer that it is wrong.** It is the right order of magnitude, it names the right tollways, and it arrives in the same format. The tool description instructs the agent to ask for dimensions before routing, and the response carries a warning when they are missing. If you call the underlying REST endpoint directly, nothing will warn you. **Response** ```json theme={null} { "distance_m": 498900, "duration_s": 18420, "transport_mode": "truck", "tolls": { "total": 55.0, "currency": "USD", "points": 4, "systems": ["VETERANS MEMORIAL TOLLWAY", "REAGAN MEMORIAL TOLLWAY"] }, "computed_for": { "profile": "truck (411 cm high, 36287 kg, 1615 cm long, 5 axles)", "departure": "now", "routing_mode": "fast", "computed_at": "2026-08-01T09:14:02Z" } } ``` `tolls.total` is the sum of all fares on the route, in `tolls.currency`. `tolls.points` counts individual fares, not distinct toll systems — one tollway can charge at several gantries, which is why four points map to two systems above. A route with no tolls returns `total: 0` and `points: 0`. Results are not deterministic and are not intended to be. Travel time depends on conditions at the time of the call. `computed_for` states what the number was computed under, so the result can be reproduced and audited. **Empty result:** no route returns an empty result rather than an error. For a truck this usually means the dimensions make every path illegal, not that no road exists. **Not available:** departure time scheduling, waypoints, route geometry (polyline), and traffic analytics. Asset-based pricing, US Traffic Analytics and the Safety Cameras Feed require an enterprise HERE entitlement and are not exposed. *** ## `placematic_geocode` Turns a complete postal address into coordinates. The full postal address or place name. Minimum 3 characters, maximum 300. Number of candidate matches to return, ordered by match quality. 1–10. **Response** ```json theme={null} { "matches": [ { "label": "1250 N Mittel Blvd, Wood Dale, IL 60191-1014, United States", "latitude": 41.98708, "longitude": -87.98763, "access_latitude": 41.98663, "access_longitude": -87.98746, "estimated_point": false, "match_quality": 0.99 } ] } ``` This tool matches complete postal addresses. Vague descriptors — "downtown Chicago", "near the airport", "the north side" — are not addresses. The geocoder tokenises them and returns a confident match that can be in the wrong state: "downtown Chicago" resolves to a street in Omaha, Nebraska. This is correct behaviour, not a defect. A geocoder has no "I don't know" mode; its job is to return the best available match, and it always finds one. Resolve vague locations to a city and state before calling. **Access point.** Where HERE has surveyed access data, the response carries an access point separate from the building position. The position is the building or rooftop centroid; the access point is where the property meets the road network. Across a sample of 20 Illinois warehouse and distribution addresses, 19 returned an access point, with a median offset of 51 m from the rooftop position and a maximum of 166 m. Addresses flagged `estimatedPointAddress` return an access point that is a projection onto the road centreline and carries no additional information — both such addresses in the sample had the smallest offsets in the set. Whether that offset changes a route depends on whether the two points snap to the same road segment. On three addresses tested, one produced truck routes 2.6 km apart approaching from different sides of the site; the other two produced routes within 60 m of each other. Use the access point where one is present — it is never worse, and occasionally it is the difference between a dock and a fence. **Empty result:** no match returns an empty `matches` array with guidance to add a city or postal code. *** ## `placematic_reverse_geocode` Turns coordinates into the nearest postal address. Decimal degrees, WGS84. Positive north of the equator. Decimal degrees, WGS84. Positive east of Greenwich — negative in the United States. Number of nearby addresses to return, nearest first. 1–10. **Response**: same shape as `placematic_geocode`. **Empty result:** offshore or unmapped coordinates return an empty `matches` array. *** ## Errors Tool-level failures return `isError: true` with a plain-language message the agent can act on. | Condition | Agent-visible behaviour | | ------------------------ | --------------------------------------------------------- | | Upstream quota exhausted | Reports the quota is reached. Does not retry. | | Token rejected upstream | Reports the key was rejected. Does not retry. | | Upstream 5xx | Reports a temporary error and suggests one retry. | | Upstream timeout | Reports no response in time and suggests one retry. | | Invalid arguments | JSON-RPC error `-32602` before any upstream call is made. | Argument validation happens in the MCP layer. A malformed call costs no transaction. *** ## Not yet available Documented here so you do not design around them. `placematic_search_nearby` — your locations within a radius of a point. Planned. `placematic_search_address` — ranked candidates for a partial or ambiguous string. Planned, and the correct tool for the "downtown Chicago" case above. Dataset version in responses — planned in the REST layer. # Troubleshooting Source: https://docs.placematic.com/mcp/troubleshooting Failure modes on the Placematic MCP Server, what each one looks like, and what actually causes it. Every entry below is a failure we hit while building and deploying this server. The symptom is rarely descriptive, which is why the causes are written out in full. Most often the client is presenting a different `Host` than the one you tested. The server validates the hostname on every request and returns `403` for anything it does not serve, with a message naming the rejected host — but that message is inside the response body, and most clients surface only a generic connection error. Confirm the URL in the client matches exactly, including the scheme and the `/mcp` path. A connector configured against an older address will keep failing after the address changes. The client is probing for OAuth. Before connecting, Claude and ChatGPT request `/.well-known/oauth-protected-resource`, `/.well-known/oauth-authorization-server` and `/register`. Those paths return `404` on this server, which is correct — it signals that no authorization flow is required. A `401` there would instead be read as "OAuth is required but you are not authenticated", and the client would abandon the connection. If you are proxying the endpoint through your own infrastructure, make sure your proxy does not convert those `404` responses into `401` or `403`. That single change breaks every connector. If the endpoint is behind a CDN or WAF configured for browser traffic, bot protection may drop server-to-server clients before they reach the application. MCP clients are backend HTTP clients, not browsers — they have no browser fingerprint and will be scored accordingly. The signature is distinctive: your own `curl` from a laptop succeeds, the client fails, and there is no corresponding entry in the application log because the request was terminated at the edge. Check the edge security event log rather than the application log. The response is Server-Sent Events, not bare JSON. `jq` is choking on `event: message`. Pipe through `sed` first: ```bash theme={null} curl -s ... | sed -n 's/^data: //p' | jq . ``` Requesting `Accept: application/json` alone does not fix this — it returns `Not Acceptable`. The `Accept` header must list both `application/json` and `text/event-stream`. There is no `result` in the response, which means there is an `error` you are not looking at. Replace the filter with `jq .` to see the whole payload. The error object carries a message that usually names the exact problem. You are sending `MCP-Protocol-Version: 2026-07-28` with a request body that lacks the envelope that revision requires. In `2026-07-28` there is no `initialize` handshake — the protocol version, client info and client capabilities travel in `_meta` on **every** request. ```json theme={null} "params": { "_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientInfo": { "name": "your-client", "version": "1.0" }, "io.modelcontextprotocol/clientCapabilities": {} } } ``` Real clients build this for you. It only comes up when hand-writing `curl`. The simpler option for a smoke test is to omit the `MCP-Protocol-Version` header entirely — the server handles the older protocol era, which does not require the envelope. The client is calling with an argument name the tool no longer has. Clients cache tool schemas at connection time and do not refresh them on their own — after a schema change, the agent keeps sending the old arguments indefinitely. The error message names the argument the server expects, and the agent will often try to "correct" itself back to the cached name, producing the same error in a loop. **Cursor and Claude Code:** restart the client. **Claude and ChatGPT connectors:** remove the connector and add it again. Toggling it off and on in a conversation does not refetch the schema. Both Claude and ChatGPT ship general-purpose place lookup. For a plain geocoding question, the model may well choose the built-in tool — it is not wrong to do so, and it will not tell you which one it used unless asked. Questions that only Placematic can answer — territory assignment, delivery terms, truck-legal routing with dimensions — do not have this problem, because no built-in tool covers them. If you need certainty about provenance for a given question, ask the agent to state which tool it used, or check the tool call record in the client. The address was not an address. Vague descriptors like "downtown Chicago" get tokenised, and the geocoder matches whatever fragment it can — in that specific case, a street in Omaha, Nebraska. Geocoding has no "I don't know" mode; it returns the best available match. Resolve vague locations to a city and state before calling, or ask the user which specific address they mean. Check whether dimensions were passed. `transport_mode: "truck"` alone selects a routing engine; it does not supply the vehicle profile. Without height, weight and length, the result is a generic truck route. The difference is measurable: for one 28-mile dispatch, adding full dimensions returned a route 1.6 miles shorter and ten minutes slower, because it avoided roads the vehicle could not legally use. A dispatcher planning against the dimensionless figure is late. On Business, Enterprise and Edu plans it is a workspace setting, not a personal one. An administrator enables it under **Workspace Settings → Permissions & Roles → Connected Data**. No amount of searching personal settings will surface it. On personal plans it is under **Settings → Apps → Advanced settings**. It is web-only — the desktop and mobile apps do not offer custom connectors. ## Still stuck Email `hello@placematic.com` with the request you sent, the full response body including headers, and the client and version. During beta, MCP Server support is best-effort and is not covered by the response times in the [Placematic SLA](https://placematic.com/sla/). # Address Validation and Data Quality Source: https://docs.placematic.com/use-cases/address-validation Why some of your addresses are wrong, how confidence scores prevent silent corruption, and the normalization step that does more than any API. # Address Validation and Data Quality A geocoder always returns something. That is the problem. Feed it garbage and it returns a coordinate — plausible, well-formed, and wrong. Persist it without the confidence score and you have converted a guess into a fact that every downstream system now trusts. ## The problem Failed deliveries. Returned parcels. Routes to the wrong side of town. Revenue maps with a mysterious cluster in a lake. All of it traces back to the same place: an address that was never validated, geocoded to a fallback, and stored as truth. The costs compound quietly: * **Operational** — a failed delivery costs more than the margin on the order * **Analytical** — a low-confidence geocode corrupts every aggregation it enters * **Routing** — a route computed to a centroid is a route to nowhere * **Trust** — nobody re-audits a coordinate that looks like a coordinate The most dangerous geocoding result is not an error. It is a city-centroid fallback for a house number that does not exist, stored without its confidence score, indistinguishable from a rooftop match. ## Who this is for Data engineers. E-commerce and logistics platform teams. Anyone whose system stores customer addresses and later acts on them. ## Recommended architecture ```mermaid theme={null} flowchart TB IN[Raw address input] --> NORM[Normalize
trim, case-fold,
expand abbreviations] NORM --> DEDUP[Deduplicate] DEDUP --> CHK{In cache?} CHK -->|Yes| DONE[Return cached] CHK -->|No| GEO[Geocode] GEO --> SCORE{Confidence
above threshold?} SCORE -->|Yes| STORE[(Store: coords,
normalized address,
score, result type,
geocoded_at)] SCORE -->|No| QUEUE[Exception queue
for review] QUEUE --> HUMAN[Human or
customer correction] HUMAN --> NORM STORE --> DONE ``` Three things are non-negotiable in that diagram: normalization before geocoding, the confidence threshold, and the exception queue. ## Relevant HERE APIs, and why **[Geocoding](/guides/geocoding)** — `/geocode` for known addresses. **Why:** it returns not just coordinates but result scoring and result types. Both are the point. **[Batch Geocoding](/guides/batch-geocoding)** — for the backfill. **Why:** four million existing addresses, nothing waiting. It is a job API — create, start, poll, retrieve, delete — not a bulk endpoint. **Autocomplete** — prevention at the source. **Why:** an address entered from a validated suggestion list does not need correcting later. `/autocomplete` completes *addresses*; `/autosuggest` handles misspellings and suggests *places*. For an address field you want the former. **Result type filtering.** A query near a shopping centre may resolve to the place, not the street address. If you need a street address, say so. **House-number fallback policy.** HERE supports falling back when an exact house number is unavailable. Whether that is acceptable depends entirely on whether you are shipping a parcel or scoring a site. Decide explicitly. ## Implementation flow 1. **Normalize at write time.** Trim, case-fold, expand `St` → `Street`. This raises your cache hit rate for free and reduces records that fail to match. 2. **Deduplicate before anything else.** A raw order export contains the same address hundreds of times. Geocoding four million rows containing 900,000 distinct addresses bills for four million. 3. **Check the cache.** Keyed on the *normalized* address, so `123 Main St` and `123 Main Street` hit the same entry. 4. **Geocode the miss.** 5. **Read the confidence score and result type.** Do not skip this step. It is the entire page. 6. **Threshold.** Above it, store. Below it, queue for review. 7. **Store the full record**: coordinates, normalized address, score, result type, and `geocoded_at`. 8. **Prevent at the source** with autocomplete on entry. The correct end state: every address has coordinates, a normalized form, a confidence score, and a timestamp. New addresses arrive at a trickle and are geocoded in real time. The batch job runs once, at migration, and then rarely. ## Cost considerations **Deduplicate the input.** The single largest waste in address processing. Order exports repeat enormously. **Cache permanently.** Addresses do not move. There is no TTL that makes sense shorter than a map release cycle. **Batch the backfill.** Cheaper per record than real-time, and nothing is waiting. **Debounce autocomplete.** It fires on keystrokes. Undebounced, you bill once per character typed. 200–300ms. **Do not re-geocode on every order.** A repeat customer costs nothing if you cached them. **Instrument cache hit rate before anything else.** A team with a 12% hit rate does not have a pricing problem. They have a caching problem, and it is cheaper to fix than to migrate. See [Reducing Google Maps Costs](/use-cases/reducing-google-maps-costs). ## Common mistakes **Discarding the confidence score.** A low-confidence match persisted as truth. This is the mistake. **No normalization before geocoding.** Lower hit rate, more failures, worse matches. **No deduplication before batch.** Paying for repetition. **Accepting a place result in an address field.** Filter by result type. Otherwise you store a shopping centre where a street address belonged. **No exception queue.** Bad addresses silently become bad coordinates. **Undebounced autocomplete.** **Confusing `/autocomplete` with `/autosuggest`.** Addresses versus places. **Treating Batch API as a synchronous bulk endpoint.** It is a job lifecycle. **Retrying `404` on batch results.** The job has not succeeded. `204` on the errors endpoint means zero errors — not a failure. **Accepting house-number fallback silently** when you are shipping a physical parcel to that address. **Blaming the geocoder for bad input.** Garbage in. ## Production checklist * [ ] Normalization applied at write time, before any geocoding call * [ ] Input deduplicated before batch submission * [ ] Cache keyed on normalized address, hit rate instrumented * [ ] Confidence score persisted alongside coordinates * [ ] Confidence threshold defined and enforced * [ ] Result type filtering applied — address versus place * [ ] House-number fallback policy decided and documented * [ ] Exception queue exists and someone owns it * [ ] Autocomplete debounced at 200–300ms on entry forms * [ ] `geocoded_at` timestamp stored for every record * [ ] Batch job lifecycle handled: `429` queued, `204` understood, `404` not retried * [ ] Jobs deleted after retrieval ## Alternatives and trade-offs **Google Maps Platform** geocoding is competitive and its place data is better. For address validation specifically the platforms are close enough that this should not drive a migration. If your product also does consumer place discovery, Google's data quality there is categorical, not marginal. **A dedicated address validation vendor** — Smarty, Loqate, Melissa — validates against postal authority data rather than a map database. For US addresses, USPS-certified validation catches things a geocoder does not: does the house number exist on that street, is the apartment number valid, is the address deliverable. If failed deliveries are a material cost, this is a better tool than a geocoder and you may need both. Geocoding and address validation are not the same thing. A geocoder tells you where an address *is*. A validation service tells you whether it *exists* and whether mail arrives there. Do not use one for the other's job. **Postal reference data directly.** USPS, Royal Mail, and equivalents publish authoritative address files. If your volume is high and your market is single-country, this is cheaper and more authoritative than any API. It is also a data pipeline you now maintain. **libpostal** for normalization, self-hosted. Good at parsing and normalizing international address strings. Does not geocode. Pairs well with any of the above and removes normalization from your API bill entirely. **No validation at all** is defensible when the address is confirmed by the customer's own action — they receive the parcel, they answer the door. If your failure rate is low and the correction loop is cheap, do not build this. ## Related guides Endpoint selection, confidence scores, and the caching that halves the bill. The job lifecycle, and the status codes that are not errors. Cache hit rate is a cost lever before it is a quality lever. Where a discarded confidence score silently corrupts every aggregation. Also: [Reverse Geocoding](/guides/reverse-geocoding) · [Logistics Platform](/use-cases/logistics-platform) ## HERE documentation * [Geocoding & Search v7](https://www.here.com/docs/category/geocoding-search-api-v7) * [Batch API v7 quick start](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/batch-api-quick-start.html) * [Batch API limits and performance](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/limits-and-performance.html) *** Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Calculating Delivery Zones and Service Areas Source: https://docs.placematic.com/use-cases/delivery-zones Building drive-time delivery zones — why radius buffers produce confidently wrong decisions, materializing isolines into PostGIS, and the containment query that should never hit an API. # Calculating Delivery Zones and Service Areas ## The business problem "Do we deliver to this address?" Asked thousands of times a day, at checkout, where latency is revenue. The answer must be instant, consistent, and correct. Most implementations answer it with a radius. The radius is wrong, and it is wrong in a way that survives review because nobody checks whether the circle crosses a lake. A 10-mile radius around a Chicago store includes several miles of Lake Michigan. A 20-minute drive-time polygon does not. This is not a rounding error. It is a category error that produces confident, defensible, incorrect expansion decisions. ## Typical users Restaurant and grocery delivery platforms. Retail chains with local fulfilment. Field service companies. Franchise networks defining territory boundaries. Anyone who says "we serve this area." ## Recommended architecture ```mermaid theme={null} flowchart LR subgraph Offline["Offline — quarterly"] LOC[Store / depot
locations] ISO[HERE Isolines
10 / 20 / 30 min bands] SIMP[Simplify polygons] PG[(PostGIS
zone polygons)] end subgraph Online["Online — per request"] ADDR[Customer address] GC[Geocode + cache] PT[Point] CONT[ST_Contains] ANS[Serviceable?
Which band?] end LOC --> ISO --> SIMP --> PG ADDR --> GC --> PT --> CONT PG --> CONT --> ANS ``` The entire design is the boundary between those two boxes. Everything expensive happens quarterly. Everything at checkout is a local spatial query. ## Which HERE APIs, and why **[Catchment Area / Isolines](/guides/catchment-area)** — the polygon. **Why:** it is a reachability set computed against the real road network. Rivers, limited-access highways, and one-way systems shape it. A circle knows none of that. **[Geocoding](/guides/geocoding)** — address to point. **Why:** containment needs a coordinate. Cache the result permanently; the address is not moving. **[Matrix Routing](/guides/matrix-routing)** — *store assignment*, not zone definition. **Why:** two stores' 20-minute polygons overlap. "Which store serves this customer" is ambiguous by construction if you answer it with polygon membership. Answer it with actual travel time. This distinction is the one most teams miss. **Isolines define coverage. Matrix defines assignment.** Using isolines for assignment produces arbitrary results in overlap regions, which is exactly where your densest customers are. **Not [Routing](/guides/routing).** A per-customer routing call at checkout is a working implementation with a fatal cost structure. ## Implementation flow 1. **For each location**, compute isolines at your service bands — 10, 20, 30 minutes — using the transport mode you actually operate. Truck zones and car zones differ. 2. **Set departure time to a representative peak hour.** A midnight polygon applied to dinner rush is a fiction. 3. **Simplify** the polygons. They were approximations with a resolution parameter to begin with. 4. **Store in PostGIS** with a spatial index, keyed by location and band. 5. **At checkout**, geocode the address (cache hit, usually), and run `ST_Contains` against the bands. 6. **Return the band**, not just a boolean. Delivery pricing by band becomes a lookup. 7. **Assign the store** by travel time from a cached matrix, not by polygon membership. ## Data flow Polygons are **derived, versioned, materialized data**. They live in your database. They are refreshed on a schedule, not on demand. Customer coordinates are **cached, permanent**. Geocode once at first order. The checkout path touches **no external API**. That is the design goal, and it is achievable. ## Production considerations **Peak and off-peak are different polygons.** If you deliver across both, store two sets and select by time of day. Otherwise you are promising 30-minute delivery on a polygon computed at 3am. **From-origin and to-destination reachability differ.** "Which customers can we reach in 30 minutes" is not "which customers can reach us in 30 minutes." One-way streets and highway ramps make these different polygons. Ask the question you mean. **Edge addresses are genuinely uncertain.** The polygon is an approximation with a resolution setting. An address 20 metres outside the boundary is not definitively unservable. Decide your policy — buffer, manual review, or accept — deliberately. **Version your zones.** When a customer disputes "you delivered here last month," you need to know which polygon was live. **Recompute on events, not on a timer.** New location, closed location, changed service promise, major road network change. Quarterly is generous otherwise. If your system calls an isoline API during a serviceability check, you have built a spatial database with an API bill attached. Materialize the polygons and the checkout path becomes free. ## Scaling **A 400-store network with three bands is 1,200 isoline calls per quarter.** Not per day. Not per customer. The cost is bounded by locations, not by traffic. **PostGIS containment against an indexed polygon set is sub-millisecond.** It scales with your traffic for free. **The geocode cache is the only growing cost**, and it grows with distinct addresses, not with orders. A repeat customer costs nothing. **Overlapping polygons in dense markets** grow the containment result set. Return all matching bands, then resolve assignment by travel time. ## Cost optimization 1. **Materialize.** Every isoline call at request time is waste. 2. **Cache geocoding permanently**, keyed on normalized address. 3. **Deduplicate addresses** before batch geocoding your existing customer table. 4. **Simplify polygons** before storage. Smaller geometry, faster queries, no accuracy loss you were entitled to anyway. 5. **Coarse resolution for coverage maps.** The map renders identically at the zoom shown. 6. **Cache the store-assignment matrix.** Store locations do not move. Cost becomes a function of your location count, which is a business number you already know. ## Common mistakes **Radius buffers.** Lakes, rivers, limited-access highways. **Calling the isoline API at checkout.** **Ignoring departure time.** **Car isolines for a truck-based service.** **Assigning customers to stores by polygon membership.** Overlaps make this arbitrary. **Requesting distance range when you meant time range.** Both valid. One answers your question. **Confusing from-origin with to-destination reachability.** **Treating polygon vertices as authoritative for edge addresses.** **Not versioning zones.** Then disputing history. **Re-geocoding known customer addresses on every order.** ## Alternatives — honestly **Google Maps Platform** offers distance and duration APIs from which you could construct a coverage approximation, but it does not expose isoline polygons in an equivalent form. For this use case HERE is the more direct fit. **Building it yourself on OSRM** with an isochrone library is entirely feasible and genuinely free, if you already run OSM infrastructure. You inherit map freshness and truck attribution as engineering. For a delivery platform where zones change quarterly, the cost of the API is likely below the cost of the maintenance. **A radius really is fine** if your delivery area is a dense uniform grid, distances are short, and the stakes are low. A 5-minute walking radius in central Manhattan is close to a circle. Be honest about which case you are in — the isoline call costs money and complexity. **For pure containment against hand-drawn zones** — where a human drew the boundary and the road network is irrelevant — you need no routing API at all. PostGIS and a polygon editor. Do not buy an API to solve a problem you have already solved. ## Related guides Isolines, range types, resolution, and why the polygon is an approximation. The right tool for assigning customers to locations. Caching, normalization, and the confidence score you should be storing. Backfilling coordinates for an existing customer table. Also: [Restaurant Delivery](/use-cases/restaurant-delivery) · [Store Locator](/use-cases/store-locator) · [Site Selection](/use-cases/site-selection) ## HERE documentation * [Routing API v8](https://www.here.com/docs/category/routing-api-v8) — isoline routing * [Geocoding & Search v7](https://www.here.com/docs/category/geocoding-search-api-v7) ## Placematic * [Catchment Area](https://placematic.com/here-location-services/catchment-area/) * [UpInsight — spatial analytics](https://placematic.com/spatial-data-analytics/) *** See the packaged solution for your industry: [Logistics & Delivery](https://placematic.com/solutions/logistics/) Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate costs, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Building an ELD Platform Source: https://docs.placematic.com/use-cases/eld-platform Location architecture for electronic logging devices — HOS coupling, IFTA jurisdiction miles, defensible route reconstruction, and the audit artifact you must be able to produce. # Building an ELD Platform An ELD platform's location layer has one property that distinguishes it from every other fleet system: **a regulator may ask you to prove it.** Not "show me the route." Prove which road segments the vehicle travelled, on what date, against which map, and why you believe it. That requirement shapes the architecture more than routing does. ## The problem Three obligations, each with a different location dependency: **Hours of Service.** Drive time is computed from the vehicle's motion. But *projected* HOS feasibility — can this driver legally complete this route — depends on the route. Change the route, change the projection. **IFTA.** Miles driven per jurisdiction, reported quarterly, subject to audit. This requires the *travelled* road segments, attributed to states or provinces. A nearest-address lookup cannot produce this. **Route planning.** Trucks. Physical constraints. A route your platform produces that a driver cannot legally or physically drive is a defect with consequences beyond the software. [Reverse geocoding](/guides/reverse-geocoding) resolves a coordinate to a nearby address. It cannot tell you which road the vehicle drove, and it will not survive an audit. This distinction is the most consequential architectural fact on this page. ## Who this is for ELD vendors. Fleet compliance platforms. Telematics providers adding compliance modules. Carriers building in-house. ## Recommended architecture ```mermaid theme={null} flowchart TB subgraph Ingest["Ingest — cheap, always"] GPS[GPS packets
10s interval] RAW[(Raw trace store
immutable)] end subgraph Segment["Trip segmentation — batch"] TRIP[Detect trips
drop idle] DOWN[Downsample] end subgraph Reconstruct["Reconstruction — batch, nightly"] RM[Route Matching
driven segments] JUR[Jurisdiction attribution] SPEED[Speed vs posted limit
on matched segment] end subgraph Plan["Planning — on demand"] TR[Truck Routing
with constraints] HOS[HOS feasibility
projection] end subgraph Audit["Audit artifact"] ART[Raw trace +
matched segments +
map release version +
match timestamp] end GPS --> RAW --> TRIP --> DOWN --> RM RM --> JUR RM --> SPEED RM --> ART RAW --> ART TR --> HOS ``` Nothing expensive touches a GPS packet on arrival. Everything defensible happens in batch. ## Relevant HERE APIs, and why **[Route Matching](/guides/route-matching)** — the compliance core. **Why:** it returns the road segments the vehicle actually travelled, with attributes attached. Jurisdiction attribution and speed compliance both require the *matched* segment, not the nearest road. This is the API that makes an ELD platform auditable. **[Truck Routing](/guides/truck-routing)** — planning. **Why:** `transportMode=truck` selects the engine. Height in centimetres, `grossWeight` in kilograms, `axleCount` including trailers describe the vehicle. Omit them and HERE returns a route no truck can drive, with no warning. **[Reverse Geocoding](/guides/reverse-geocoding)** — stop labels for humans. **Why:** dispatchers read addresses. Use `POST /multi-revgeocode` on detected stops, never on packet ingest. **[Matrix Routing](/guides/matrix-routing)** — if you also do dispatch. See [Fleet Routing](/use-cases/fleet-routing). ## The audit artifact This is the section that justifies the architecture. For any trip a regulator might question, you must be able to produce: 1. **The raw trace** — the observations, unmodified 2. **The matched segments** — your reconstruction 3. **The map release version** used to produce the match 4. **The timestamp** of matching A trip matched in March against a March map may match differently in September against a September map. Both matches can be correct. Without recording which map produced which result, you cannot explain the discrepancy — and you will be asked to. Matched output is a **derivation**. Keep the source. When a driver, a carrier, or an auditor disputes the reconstruction, the raw trace is your only defence. ## Implementation flow 1. **Ingest GPS. Do nothing expensive.** Store it. No geocoding, no matching, no positioning. 2. **Segment trips on close.** Detect start, stop, and idle. Drop the parked portions — a vehicle idling for four hours emits thousands of points describing a parking space. 3. **Downsample.** Beyond a certain density, additional points add cost and no information. 4. **Match, in batch, overnight.** Nothing is waiting. 5. **Attribute segments to jurisdictions.** This feeds IFTA. 6. **Compare observed speed against the matched segment's posted limit.** Not the nearest road's. 7. **Persist the full audit artifact.** 8. **Plan routes with truck constraints**, and couple HOS projections to the route explicitly. ## HOS and routing are coupled Most ELD platforms model these as independent modules. They are not. If the planned route changes, the projected drive time changes, and the driver's remaining hours change. A dispatcher who reroutes at 2pm has altered the HOS projection whether or not the system says so. Model the dependency explicitly. A route object should carry its HOS implications, or the two systems will disagree and the driver will trust neither. Test that a re-route triggers an HOS recomputation. It is a one-line test and it catches a class of bug that surfaces during an audit rather than during QA. ## Cost considerations **Never reverse-geocode a GPS packet.** A 200-vehicle fleet at a ten-second ping rate produces 648,000 points a day. Any per-point API call multiplies by that number. Geocode detected stops. Instrument the ratio: reverse-geocode calls to detected stops should be near 1. **Segment before matching.** In real telematics feeds, dropping idle periods removes a substantial fraction of submitted points. Do this in your pipeline, not in HERE's. **Downsample dense traces.** Sub-second sampling submitted raw is waste. **Match trips, not packets.** Matching a partial trace and re-matching as points arrive burns transactions and produces inconsistent history. **Store the matched result.** Re-matching for a report six months later bills again *and* may produce a different answer against an updated map. **Batch everything retrospective.** Latency is worth nothing to you here and it costs money. **Evaluate asset-based pricing.** An ELD platform has a countable tracked-vehicle population and unpredictable call volume. Call-volume pricing punishes a dispatcher for operational diligence. See [HERE Pricing Explained](/getting-started/here-pricing-explained). Asset-based pricing availability depends on contract tier. Confirm before it becomes load-bearing in your unit economics. ## Common mistakes **Using reverse geocoding to reconstruct driven routes.** It returns addresses. Audits ask about road segments. **Reverse-geocoding on packet ingest.** The dominant cost error in telematics. **Matching packets instead of trips.** **Submitting the parked portion of a trace.** **Not persisting the raw trace.** The matched output is a derivation. **Not recording the map release version** on compliance records. **Comparing GPS speed against the nearest road's limit.** False violations on frontage roads parallel to highways. Drivers notice. So do lawyers. **Treating HOS and routing as independent.** **Setting `transportMode=truck` and stopping there.** The engine is selected. The vehicle is not described. **Passing metres where centimetres are expected.** A four-centimetre truck routes anywhere. **Ignoring sampling rate and blaming the matcher.** Sampling rate is the dominant quality input, and it is a device configuration decision made before any API is called. ## Production checklist * [ ] GPS ingest does nothing expensive per packet * [ ] Raw traces stored immutably, retained per your compliance obligation * [ ] Trip segmentation drops idle periods before matching * [ ] Traces downsampled before submission * [ ] Matching runs as a batch job on trip close * [ ] Audit artifact persisted: raw trace, matched segments, map release version, match timestamp * [ ] Speed compliance computed against the matched segment, not the nearest road * [ ] Reverse geocoding triggered by stop detection; call-to-stop ratio near 1 * [ ] Truck constraints stored on the vehicle record, applied to every routing call * [ ] Trap-geometry assertions in CI: 11foot8 bridge (Durham NC), Storrow Drive (Boston), Southern State Parkway (Long Island) — with a `car`-mode control * [ ] Re-route triggers HOS recomputation * [ ] Low-confidence matches surfaced, not silently persisted ## Alternatives and trade-offs **Google Maps Platform** has no equivalent to Route Matching. Without matched road segments there is no defensible jurisdiction-mile calculation and no reliable speed compliance. Its Large Vehicle Routing, added in August 2026, expresses commercial vehicle constraints for the contiguous US but returns no posted speed limits — so it cannot feed speed compliance either. For an ELD platform's core, it is not a candidate. It remains fine for any consumer-facing address autocomplete you expose. **Self-hosted map matching** — OSRM's matching service, Valhalla's Meili — is genuinely capable. You inherit map freshness, truck attribute curation, and jurisdiction boundary maintenance as ongoing engineering. Realistic if you have a GIS team. For most ELD vendors this becomes a second product nobody is funded to own, and the map version you must cite in an audit is one you now maintain. **A dedicated IFTA vendor** solves the reporting workflow, jurisdiction boundaries, and filing. If IFTA is a checkbox rather than a differentiator, buy it. You still need matched segments to feed it. **Nearest-road snapping** instead of proper map matching is used, and it is wrong. It fails at interchanges, on divided highways, and on frontage roads — precisely the geometry that generates disputed miles. **Positioning APIs** do not help here. Hybrid radio positioning produces estimates with uncertainty radii; feeding them to a map matcher produces confident nonsense. See [Positioning](/guides/positioning). ## Related guides Map matching, IFTA, and why the matched segment is the only defensible answer. Constraints, units, and the trap geometry that proves they apply. Ingesting GPS at scale without touching an API per packet. The dispatch layer alongside the compliance layer. Also: [Reverse Geocoding](/guides/reverse-geocoding) · [Hazmat Routing](/use-cases/hazmat-routing) · [HERE Pricing Explained](/getting-started/here-pricing-explained) ## HERE documentation * [HERE Routing API v8](https://www.here.com/docs/category/routing-api-v8) * [Routing API v8 reference](https://docs.here.com/routing/reference/routing-api-v8-calculateroutes) ## Placematic * [Truck Routing](https://placematic.com/here-location-services/truck-routing/) * [Route Matching](https://placematic.com/here-location-services/here-route-matching/) *** See the packaged solution for your industry: [Fleet & ELD](https://placematic.com/solutions/fleet-eld/) Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial systems. We have deployed HERE into production ELD systems. [Talk to us](https://placematic.com/contact/). # Embedding a Routing API in Your SaaS Product Source: https://docs.placematic.com/use-cases/embedding-routing-api Building location features into a multi-tenant B2B product — tenant isolation, quota allocation, the rate limit you share, and the reselling question you must answer before you ship. # Embedding a Routing API in Your SaaS Product You are not a fleet company. You sell software to fleet companies. That changes almost every architectural decision below, and it introduces one commercial question that must be answered before a line of code is written: **are you permitted to expose this API to your customers?** ## The problem Your product needs routing, geocoding, or maps. Your customers use it. From HERE's perspective there is one contract — yours. Which means: * One tenant's bulk operation can rate-limit another tenant's dispatch * One tenant's inefficient integration appears on your invoice * One shared geocode cache is a data leak waiting for a curious engineer * Your unit economics invert if you bill per shipment and pay per API call None of this is a routing problem. All of it is the reason embedding is different from building. ## Who this is for B2B SaaS product and engineering leaders adding location features. TMS, ELD, field service, delivery, and logistics platform vendors. Anyone whose customers will consume location functionality through your product rather than calling HERE directly. ## Recommended architecture ```mermaid theme={null} flowchart TB subgraph Tenants["Your customers"] T1[Tenant A] T2[Tenant B] T3[Tenant C] end subgraph Yours["Your platform"] GW[API gateway
per-tenant quota + budget] ISO[Tenant isolation layer] CACHE[(Geocode cache
tenant-scoped keys)] MXC[(Matrix cache
tenant-scoped)] QUEUE[Priority queue
dispatch > bulk] end subgraph HERE["HERE — one contract"] API[Routing / Matrix / Geocoding] end T1 --> GW T2 --> GW T3 --> GW GW --> ISO ISO --> CACHE ISO --> MXC CACHE -->|miss| QUEUE MXC -->|miss| QUEUE QUEUE --> API ``` **The gateway is not optional.** Without a per-tenant budget enforced before the request leaves your infrastructure, your largest customer degrades service for everyone and you find out from a support ticket. ## Relevant HERE APIs, and why **[Routing](/guides/routing)** — the leg your customer's vehicle drives. `transportMode` and vehicle constraints are **tenant data**, not defaults in your request builder. **[Matrix Routing](/guides/matrix-routing)** — where your customers unknowingly loop. If your product exposes "distance between these locations," someone will call it in a loop. Expose a batch endpoint that maps to a matrix call, or you will pay for their loop. **[Geocoding](/guides/geocoding)** — cached per tenant. Never globally. **[Truck Routing](/guides/truck-routing)** — if any customer routes commercial vehicles, vehicle profiles must be tenant-scoped model data. A hardcoded height in your routing service is a bridge strike in someone else's fleet. **[Authentication](/getting-started/authentication)** — your customers never see a HERE key. Your platform holds one credential. Their access control is yours to build. ## Implementation flow 1. **Answer the reselling question first.** See below. It is a contract term, not an implementation detail. 2. **Build the gateway before the feature.** Per-tenant quota, budget, and priority queue. 3. **Scope every cache by tenant.** Geocode, matrix, isoline, route. 4. **Model tenant configuration.** Vehicle profiles, transport modes, service rules. All tenant data. 5. **Expose batch shapes, not loops.** If your API returns one route, your customer will call it a thousand times. 6. **Instrument per-tenant call volume** from day one. You cannot price a feature whose cost you cannot attribute. 7. **Decide your billing model** before you launch, not after a customer discovers unlimited routing. ## The reselling question Exposing HERE APIs to your customers may constitute redistribution, which carries different licensing terms than internal use. **Resolve this before you build.** Discovering it during a customer's security review, or during your own contract renewal, is expensive. There is a spectrum: * **Internal consumption** — your platform calls HERE, renders a map, shows an ETA. Your customer sees a feature. * **Pass-through** — your API returns HERE's response shape to your customer's engineers. * **Redistribution** — your customer builds against what is effectively a HERE proxy. The further right you sit, the more likely you need explicit terms. Placematic USA LLC is the contracting party for HERE services; this is exactly the conversation a Gold Partner exists to have with you before you ship, not after. ## Cost considerations **Your invoice is the sum of your customers' inefficiency.** A tenant that reverse-geocodes every GPS ping generates a bill you pay and they do not see. Two responses: * Fix it at the gateway: enforce sane call patterns, cache aggressively, expose only the shapes you can afford. * Meter and pass through: attribute cost per tenant, and price the feature accordingly. Most platforms do the first and discover they never needed the second. **Your billing unit and your cost unit must not diverge.** Billing per shipment while paying per API call means a customer with an inefficient integration destroys your margin on that account. Either bound the calls per shipment, or bill on something correlated with them. **Evaluate asset-based pricing** if your customers' vehicle populations are countable and their call volumes are not. See [HERE Pricing Explained](/getting-started/here-pricing-explained). **Rate limits are shared.** HERE sees one contract. Queue and prioritize at your layer: dispatch traffic ahead of bulk onboarding, always. **Batch onboarding is bursty.** A large tenant arrives with a million addresses. Batch API concurrency limits are per-contract. Queue onboarding jobs so they do not starve nightly enrichment for every other tenant. ## Common mistakes **A shared geocode cache across tenants.** The tempting optimization — addresses are addresses — is where multi-tenant spatial systems leak. Tenant A's customer list is inferable from cache hit patterns. The savings were never worth it. **No per-tenant call budget.** One tenant `429`s everyone. **Vehicle constraints hardcoded in the routing service.** They belong to the tenant, and to the load. **Exposing a single-route endpoint** and being surprised when customers loop it. **Building the feature before answering the reselling question.** **No per-tenant cost attribution.** You cannot price what you cannot measure. **Passing HERE error codes through unchanged.** Your customer does not have a HERE contract and cannot act on a `403` entitlement error. Translate it. **Assuming your customers will use the API correctly.** They will not. Design the surface so that misuse is bounded. **Treating rate limits as a technical problem.** A persistent `429` means your contracted quota does not match your aggregate workload. That is a commercial conversation. ## Production checklist * [ ] Reselling and redistribution terms confirmed in writing * [ ] Per-tenant quota and budget enforced at the gateway, before egress * [ ] Every cache keyed by tenant; verified with a cross-tenant test * [ ] Priority queue: interactive dispatch ahead of bulk operations * [ ] Vehicle profiles and transport modes stored as tenant configuration * [ ] Batch-shaped endpoints exposed instead of single-item endpoints * [ ] Per-tenant call volume and cost attributed and dashboarded * [ ] HERE error codes translated into terms your customer can act on * [ ] Onboarding jobs isolated from nightly enrichment concurrency * [ ] Billing unit correlated with cost unit ## Alternatives and trade-offs **Have your customers bring their own key.** Clean commercially — their contract, their invoice, their rate limit. Terrible for onboarding: every customer now runs a procurement process before they can use your feature. This kills product-led growth. It is the right answer for enterprise-only products with long sales cycles, and the wrong answer for everyone else. **Self-host OSRM** and eliminate the licensing question entirely. You now maintain truck attributes, traffic, and map freshness for every customer. Viable if location is your core competency. If you are a TMS vendor, it is a second product nobody is funded to own. **Google Maps Platform** has well-trodden terms for embedding and a large ecosystem. It cannot express commercial vehicle constraints. If any of your customers route trucks, this decides it. **Mapbox** offers strong developer experience and clear embedding terms. Same constraint gap. **Build only what your customers cannot.** The strongest version of this architecture exposes *your* domain logic — territories, delivery zones, dispatch — computed on materialized spatial data in your own database, with HERE calls bounded and rare. See [Delivery Zones](/use-cases/delivery-zones) for the materialization pattern. ## Related guides Key handling, rotation, and why `403` is not `401`. The batch shape to expose so customers do not loop. Call volume versus asset-based, and why your billing unit matters. The multi-tenant TMS architecture in full. Also: [Reducing Google Maps Costs](/use-cases/reducing-google-maps-costs) · [Getting a HERE API Key](/getting-started/getting-a-here-api-key) · [Truck Routing](/guides/truck-routing) *** See the packaged solution for your industry: [Logistics & Delivery](https://placematic.com/solutions/logistics/) Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial systems. As a HERE Gold Partner, Placematic USA LLC is the contracting party — which makes the redistribution conversation a short one. [Talk to us](https://placematic.com/contact/). # Calculating Accurate ETAs Source: https://docs.placematic.com/use-cases/eta-calculation Why your ETA is wrong — routing duration is not a delivery promise, traffic is a departure-time parameter, and the dwell time no API knows about. # Calculating Accurate ETAs A routing API returns a travel duration. A customer receives a promise. The gap between those two things is where every ETA system fails, and no amount of vendor switching closes it. ## The problem Your ETA is consistently optimistic. Customers notice. Support absorbs it. The instinct is to blame the routing engine. Usually the routing engine is fine. What is missing is everything the routing engine cannot know: * **Service time** at the stop — unloading, signature, elevator, the customer who takes four minutes to answer the door * **Preparation time** before departure — food cooking, order picking, vehicle loading * **Dwell time** between arrival and the next departure * **Traffic at the departure time**, not at the time you computed the route * **Driver behaviour** — breaks, deviations, the second coffee A routing duration is the time a vehicle spends moving. That is rarely the number the customer cares about. Presenting a routing duration as an ETA is the most common source of ETA inaccuracy, and it is a modelling error, not an API error. It survives migration. ## Who this is for Product engineers on delivery, logistics, field service, and dispatch systems. Anyone whose UI shows an arrival time. ## Recommended architecture ```mermaid theme={null} flowchart LR subgraph Model["ETA = sum of parts"] PREP[Preparation time
your data] TRAVEL[Travel duration
routing API] SERVICE[Service time
your data, per job type] BUFFER[Uncertainty buffer
your history] end subgraph Refresh["Live refresh"] GPS[Current position] REMAIN[Remaining route
geometry] RECALC[Recompute remaining
duration] end subgraph Feedback["The loop that matters"] ACTUAL[Actual arrival] DIFF[Actual − predicted] LEARN[Update service time
estimates per job type] end PREP --> TRAVEL --> SERVICE --> BUFFER GPS --> REMAIN --> RECALC ACTUAL --> DIFF --> LEARN --> SERVICE ``` The feedback loop is the part teams skip and the part that produces accuracy. ## Relevant HERE APIs, and why **[Routing](/guides/routing)** — travel duration with traffic. **Why:** the moving portion. Set `departureTime` explicitly; traffic conditions at 3am and 5pm produce different durations for the same path. **[Matrix Routing](/guides/matrix-routing)** — ETAs for many origin-destination pairs. **Why:** if you are computing ETAs for a list — every courier to every order, every technician to every job — that is a matrix, not a loop of routing calls. **[Truck Routing](/guides/truck-routing)** — if the vehicle is commercial. A car route's duration is not a truck's duration, because it is not the truck's route. **Nothing else.** Preparation time, service time, and dwell time are your data. No mapping API has them. ## Implementation flow 1. **Decompose the ETA** into preparation + travel + service + buffer. Store each separately. Present the sum. 2. **Set `departureTime` explicitly** on every routing call. Never let it default when the departure is in the future. 3. **Instrument actual versus predicted** for each component, per job type, per time of day. 4. **Estimate service time from history**, not from a product manager's guess. A 20-minute estimate on a 90-minute job destroys the entire downstream sequence. 5. **Refresh from remaining geometry**, not by re-routing. Given current position and the route polyline, recompute the remaining duration locally. 6. **Recompute the route only on deviation** — the driver left the planned path. 7. **Communicate uncertainty.** A range beats a false point estimate. Instrument the residual — actual minus predicted — broken down by component. Most teams discover that travel duration is accurate to within a few percent and service time is off by 40%. That tells you where to spend the next quarter. ## Cost considerations **Do not re-route on every GPS ping.** This is the dominant ETA cost trap. A courier fleet emitting positions every ten seconds, with a routing call per ping to "refresh the ETA," generates a bill proportional to fleet size × ping rate. It is the same architectural error as [reverse-geocoding every ping](/guides/reverse-geocoding). Recompute the remaining duration from the route geometry and current position. That is arithmetic on data you already hold. Call the routing API when the driver deviates. **Batch ETA computation is a matrix.** "ETA from each of 40 couriers to this order" is one matrix call, not 40 routing calls. **Cache route geometry, not durations.** The path between two fixed points is stable for hours. The duration is not. Different TTLs, or you serve stale arrival times. **Set `return` explicitly.** Nothing consumes turn-by-turn `instructions` when you only need `summary`. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Common mistakes **Presenting routing duration as the ETA.** Preparation and service time are missing. **Letting `departureTime` default.** A route computed at 2pm for a 6pm departure inherits 2pm traffic. **Re-routing on every position update.** Expensive and unnecessary. **Guessing service time.** Instrument it. It is the largest error term. **Using car routing for commercial vehicles.** Different path, different duration. **Looping routing calls for a list of ETAs.** Matrix. **A single point estimate with no uncertainty.** "Arriving at 3:14pm" is a promise you cannot keep. "Between 3:05 and 3:25" is one you can. **No feedback loop.** Your ETA accuracy will never improve, on any platform. **Optimistic buffers.** Systems drift optimistic because nobody is punished for a late buffer and everybody is punished for a late arrival. **Ignoring dwell time between stops.** Multi-stop ETAs compound the error at every hop. ## Production checklist * [ ] ETA decomposed into preparation, travel, service, and buffer — stored separately * [ ] `departureTime` set explicitly on every future-dated routing call * [ ] Transport mode and vehicle constraints match the actual vehicle * [ ] Actual-versus-predicted instrumented per component, per job type * [ ] Service time estimates derived from history and refreshed * [ ] ETA refresh computed from remaining geometry, not by re-routing * [ ] Route recomputation triggered by deviation, not by timer * [ ] Multi-item ETA computation uses matrix, not routing loops * [ ] Uncertainty communicated to the end user as a range * [ ] Multi-stop error compounding modelled explicitly ## Alternatives and trade-offs **Google Maps Platform** offers comparable traffic-aware duration estimates. For passenger vehicles the accuracy difference is small enough that it should not drive a platform decision. For commercial vehicles the route itself differs, so the duration does too. **Historical duration from your own telematics** frequently beats any routing API for repeated lanes. If a courier drives the same depot-to-zone route daily, your own data is a better predictor than a generic model. Use the API for unfamiliar routes; use your history for familiar ones. **A learned model on top of routing duration** — gradient-boosted residuals against features like time of day, weather, and job type — is a well-trodden path. It needs volume, and it needs the instrumentation above regardless. Build the instrumentation first; the model may prove unnecessary. **No routing API at all** is viable for fixed-schedule operations. A route that runs at the same time every day, on the same path, has an ETA you can read from history. **Straight-line distance × average speed** is worse than it sounds and used more often than anyone admits. It fails at exactly the moment the customer is paying attention: dense urban delivery. ## Related guides `departureTime`, traffic, and the `200` that is not success. Many ETAs, one call. Ingesting GPS without re-routing on every packet. Where preparation time makes the routing duration nearly irrelevant. Also: [Field Service](/use-cases/field-service) · [Last-Mile Delivery](/use-cases/last-mile-delivery) · [Truck Routing](/guides/truck-routing) ## HERE documentation * [Routing API v8](https://www.here.com/docs/category/routing-api-v8) * [Matrix Routing API v8](https://www.here.com/docs/category/matrix-routing-api-v8) *** Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Building EV Charging Applications Source: https://docs.placematic.com/use-cases/ev-charging EV charging architecture on HERE — routing versus charge points, why availability and network data have different freshness, and the commercial truck charging problem nobody has solved. # Building EV Charging Applications ## The business problem An electric vehicle's ability to reach charger three depends on whether it stopped at charger one. That single sentence is why you cannot build EV routing by filtering a car route against a charger table. Energy state changes along the path, and the decision to stop is itself a routing decision. ## Typical users EV fleet operators. Charging network operators. Fleet electrification consultancies. EV navigation and range-anxiety apps. Logistics companies evaluating Class 8 electrification. ## Recommended architecture ```mermaid theme={null} flowchart TB subgraph Vehicle["Vehicle model"] BAT[Battery capacity] CURVE[Consumption curve
speed, gradient, load] SOC[Live state of charge] CONN[Connector type,
max charge rate] end subgraph Network["Charger network — slow data"] CP[EV Charge Points API] CACHE[(Cached network
long TTL)] end subgraph Live["Availability — fast data"] AVAIL[Availability query
short TTL] end subgraph Plan["Planning"] PRE[Feasibility pre-check
range vs distance] EVR[EV Routing
charging stops as output] end subgraph Out["Consumption"] DISP[Dispatch: vehicle unavailable
during charge] DRV[Driver: stop is a first-class event] end BAT --> PRE SOC --> PRE CURVE --> EVR CONN --> CP --> CACHE --> EVR PRE -->|feasible| EVR AVAIL --> EVR EVR --> DISP EVR --> DRV ``` ## Which HERE APIs, and why **[EV Routing](/guides/ev-routing)** — routes that reach their destination. **Why:** it accepts a consumption model and current charge state, and returns charging stops as routing *outputs*. This is a routing capability, not a post-processing step. **HERE EV Charge Points API** — the infrastructure. **Why:** station locations, connector types, power feeds, availability, tariffs. It is a **separate product** at [v2 and v3](https://www.here.com/docs/category/ev-charge-points-api-v3), with a documented migration path. EV routing and EV Charge Points are different APIs with different entitlements and separate bills. A key that routes EVs may return `403` from the charge points endpoint. Confirm both before you architect. See [Getting a HERE API Key](/getting-started/getting-a-here-api-key). **[Matrix Routing](/guides/matrix-routing)** — matrix responses can include consumption alongside time and distance. **Why:** for fleet assignment, "which vehicle can reach this job on current charge" is a matrix question with an energy dimension. **[Catchment Area](/guides/catchment-area)** — consumption-range isolines. **Why:** "everywhere this vehicle can reach on its current charge" is a reachability polygon with `consumption` as the range type, not time or distance. **[Tour Planning](/guides/tour-planning)** — multi-stop EV dispatch, where charging time competes with service time in the schedule. ## Implementation flow 1. **Model the vehicle properly.** Battery capacity, consumption curve as a function of speed and gradient, connector type, maximum charge rate. This is a database record. 2. **Pre-check feasibility cheaply.** Range versus straight-line distance rejects most infeasible assignments before you compute anything. 3. **Cache the charger network** with a long TTL. Stations move slowly. 4. **Query availability separately**, with a short TTL, only when it matters. 5. **Route with live state of charge** where telematics provides it. 6. **Surface charging stops explicitly** to dispatch and to the driver. 7. **Model the vehicle as unavailable** for the charge duration. ## Data flow **Two data speeds, and conflating them is the defining error of this domain.** The charger **network** — where stations are, what connectors they have — changes over months. Cache it aggressively. **Availability** — is this charger free right now — changes by the minute, and has a latency that route computation does not. A route computed against static charger data will confidently send a truck to an occupied or out-of-service charger, at the exact moment the vehicle has no range to reach an alternative. This is worse than not routing at all. ## Production considerations **Charge state is an input, not an assumption.** A vehicle at 22% produces a materially different route than the same vehicle at 90%. If telematics does not provide live SoC, say so in the UI. Do not silently substitute full battery. **Consumption is a curve, not a constant.** Linear models are optimistic on highways and catastrophically optimistic on grades. Auxiliary load — climate control, refrigeration — is not negligible on a delivery van in July. **Truck charging is not car charging.** HERE documents EV truck charging locations as a distinct concern. Connector types, power levels, and physical site access differ. A Class 8 vehicle routed against passenger charging infrastructure is routed to sites it cannot physically enter. This is not a solved problem in the market. Charging corridors for commercial vehicles are sparse. Be honest with customers about what the data supports. **Charging stops are first-class schedule events.** A driver needs to know a 45-minute stop is coming. A dispatcher needs to know the vehicle is unavailable. A route object that hides the stop inside a polyline will not survive contact with operations. **Degrade honestly.** If availability is stale, say the route assumes the charger is free. ## Scaling **Cache the network, not the availability.** This one decision determines your API bill. **Feasibility pre-checks are free and eliminate most work.** A vehicle with 80 km range does not need an EV route computed for a 300 km job. **Availability queries scale with routing volume**, not with network size. Query only the chargers on the candidate route. **Do not query availability on map pan.** Every drag becomes a request. **Corridor analysis is a batch job.** "Which of our 400 routes are electrifiable" is computed overnight, not per request. ## Cost optimization 1. **Long TTL on the charger network.** Weeks, not minutes. 2. **Short TTL on availability, queried narrowly.** Only chargers on candidate routes. 3. **Feasibility pre-check before full EV routing.** Range comparison is free. 4. **Cache EV routes for repeated corridors.** A daily depot-to-depot run does not change. 5. **Do not query charge points on map interaction.** 6. **Batch corridor and electrification analysis.** Both EV routing and EV Charge Points bill separately. Confirm entitlement for both. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Common mistakes **Filtering a car route against a charger table.** The vehicle's ability to reach charger three depends on charger one. **Assuming full charge.** Nearly every real dispatch begins partial. **Linear consumption models.** Highways and hills. **Ignoring auxiliary load.** Refrigerated vans in summer. **Routing commercial EVs against passenger charging data.** Wrong connectors, inaccessible sites. **Treating availability as static.** **Confusing EV routing with the EV Charge Points API.** Separate products, separate entitlements. **Deploying against Charge Points v2** without reading the v3 migration path. **Hiding the charging stop inside the route geometry.** **Querying charge points on every map pan.** ## Alternatives — honestly **Google Maps Platform** has limited EV routing capability with charge-state awareness. For consumer EV navigation this gap has narrowed. For commercial fleet electrification with truck-specific charging, it is not competitive. **Charging network operator APIs** — ChargePoint, Electrify America, Tesla — give you authoritative, real-time availability for *their* network, which is better than any aggregator for that network. The trade-off is coverage: you now integrate five APIs and reconcile five schemas. For a fleet standardized on one network, go direct. **Open Charge Map** is free and community-maintained. Coverage and freshness are variable. Fine for a prototype or a coverage map. Not fine for dispatching a vehicle with 40 km of range. **Build your own routing on OSRM plus a charger dataset** and you inherit the energy-aware routing problem, which is genuinely hard. This is a research project, not an integration. **For pure feasibility scoring** — "can our routes be electrified" — you may need no EV routing API at all. Historical route distances from telematics, compared against vehicle range, answers most of the question. Buy the API when you need to *dispatch*, not when you need to *analyze*. ## Related guides Consumption models, charge state, and the two-product distinction. Consumption alongside time and distance, for fleet assignment. Consumption-range isolines: everywhere reachable on current charge. When charging time competes with service time in a schedule. Also: [Fleet Routing](/use-cases/fleet-routing) · [Truck Routing](/guides/truck-routing) · [Getting a HERE API Key](/getting-started/getting-a-here-api-key) ## HERE documentation * [HERE EV Charge Points API v3](https://www.here.com/docs/category/ev-charge-points-api-v3) * [Routing API v8 reference](https://docs.here.com/routing/reference/routing-api-v8-calculateroutes) — EV routing parameters * [Matrix Routing API v8](https://www.here.com/docs/category/matrix-routing-api-v8) ## Placematic * [EV Routing](https://placematic.com/here-location-services/ev-routing/) * [HERE Location Services](https://placematic.com/here-location-services/) *** Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate costs, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Field Service Optimization Source: https://docs.placematic.com/use-cases/field-service Building field service scheduling on HERE — skills-constrained tour planning, appointment windows, the daily solve cadence, and why re-optimizing on every event destroys technician trust. # Field Service Optimization ## The business problem Twelve technicians. Sixty jobs. Some jobs need a certified electrician. Some customers are only home between 2pm and 4pm. One technician finishes at 3pm for a school run. Two jobs are emergencies that must be served today. Assign the jobs, sequence them, and produce schedules the technicians will actually follow. The last clause is the hard one. ## Typical users HVAC, plumbing, and electrical services. Telecom and utility field operations. Medical device servicing. Equipment maintenance. Home installation and repair platforms. ## Recommended architecture ```mermaid theme={null} flowchart TB subgraph SoR["System of record"] TECH[Technicians
skills, shifts, home base] JOBS[Jobs
duration, window, skill, priority] GC[(Geocode cache)] end subgraph Solve["Nightly solve"] MX[Matrix Routing
tech bases × jobs] TP[Tour Planning
skills + windows + priority] UNASSIGNED[Unassigned jobs
surfaced to dispatch] end subgraph Day["Execution"] RT[Routing v8
leg geometry] APP[Technician app] GPS[Position updates] end subgraph Replan["Exception path"] RESOLVE[Re-solve
remaining jobs only] end TECH --> MX --> TP JOBS --> GC --> MX TP --> UNASSIGNED TP --> RT --> APP GPS --> RESOLVE RESOLVE --> APP ``` ## Which HERE APIs, and why **[Tour Planning](/guides/tour-planning)** — the core. **Why:** field service *is* the Vehicle Routing Problem with time windows and skills. HERE's solver supports capacitated VRP, time windows, multi-depot, heterogeneous fleets, priorities, and pickup-and-delivery as first-class variants. Skills map to vehicle-type capabilities; certifications become vehicle specialties. It is included in the HERE Base Plan. Approximating it with a heuristic is a choice, not a necessity. **[Matrix Routing](/guides/matrix-routing)** — the cost table underneath. **Why:** Tour Planning consumes travel costs. If you build your own solver, this is your interface to reality. **[Routing](/guides/routing)** — the leg the technician drives. `transportMode=car` for vans under 3.5t; [truck](/guides/truck-routing) constraints if you dispatch service trucks with height or weight restrictions. **[Geocoding](/guides/geocoding)** — customer addresses to points. Cached permanently. A repeat customer costs nothing. **[Catchment Area](/guides/catchment-area)** — technician coverage territories, if you assign geographically rather than dynamically. See [Territory Management](/use-cases/territory-management). ## Implementation flow 1. **Model technicians as vehicle types** with capabilities (skills), shifts (start/end time and location), and limits (`maxDistance`, `shiftTime`). 2. **Model jobs as tasks** with `duration` (service time), `times` (appointment windows), `demand`, and priority. 3. **Geocode job addresses** on creation. Cache. Never again. 4. **Solve nightly**, async, against the next day's job set. 5. **Handle unassigned jobs explicitly.** The solver will drop what capacity and windows cannot accommodate. Surface those to a human before the day starts, not at 3pm. 6. **Compute leg geometry** for each assigned leg. 7. **Push the schedule** to the technician app. 8. **Re-solve only on exception**, and only over remaining jobs. `fleet.types[].profile` is a **string reference** into `fleet.profiles[].name`. It is not a transport mode. This trips up nearly every first Tour Planning integration. ## Data flow Skills, shifts, and appointment windows are **constraints in the problem**, not filters applied afterward. Post-filtering a solver's output means the solver optimized against a fiction. Job service time is **as important as travel time** and is more often wrong. A 20-minute estimate on a 90-minute job destroys the entire day's sequence downstream. ## Production considerations **Solve on a cadence. Not on every event.** This is the section that matters most, and it is a human problem wearing engineering clothes. A system that re-optimizes when each new job arrives produces a schedule that changes hourly. Technicians stop trusting it. They start planning their own day. Your optimization now runs against a fleet that ignores it, and you have paid for the privilege. Nightly solve. Locked schedule. Exception-driven replanning only — emergency job, technician sick, job overran badly. Communicate every change explicitly. A stable, 90%-optimal schedule outperforms a perfect one nobody follows. **Mid-day replanning is a new problem.** Technician current location becomes the shift start. Completed jobs are removed. Fresh submission. **Service time estimates are your accuracy ceiling.** Instrument actual versus estimated duration per job type. Feed it back. This will improve your schedules more than any solver parameter. **`configuration.termination.maxTime` is not a detail.** HERE's example sets it to 2 seconds. On a 60-job, 12-technician problem that produces a legal schedule a dispatcher will reject. Raise it. Measure the marginal improvement. Find the knee. **Costs encode policy.** `fixed` cost per technician tells the solver whether adding a technician to the day is cheap or expensive. Set it deliberately. Most teams leave it at the example value and wonder why the solution uses everyone. ## Scaling **Solve time grows non-linearly with job count.** Partition by geography before you partition by anything else. Two independent metros are two problems. **Nightly solve has a real deadline.** If dispatch reviews schedules at 6am, and the solve takes 40 minutes at 200 jobs, model what happens at 600. **The matrix caches well.** Technician home bases are fixed. Customer locations repeat. Hash the input set. **Emergency insertion is a different algorithm.** Full re-solve for one urgent job is expensive and disruptive. Consider a cheap insertion heuristic for same-day emergencies and a full solve overnight. ## Cost optimization 1. **Cache geocoding permanently.** Repeat customers are free. 2. **Hash the tour problem.** Skip re-solves that would return the same schedule. 3. **Matrix, never routing loops**, for the cost table. 4. **Do not re-solve on every job creation.** Cost and trust, both. 5. **Batch-geocode the historical customer table once**, deduplicated. 6. **Set `return` explicitly** on leg routing. Nothing consumes `instructions` server-side. Tour Planning is in the Base Plan. The cost of field service optimization is dominated by geocoding volume and solve frequency, not by the solver. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Common mistakes **Re-optimizing on every event.** The schedule becomes noise. Technicians route around you. **Building a nearest-neighbour heuristic** to avoid a licensing conversation that does not exist. **Treating skills as a post-filter** rather than a solver constraint. **Leaving `maxTime: 2` from the documentation example.** **Ignoring unassigned jobs** until a customer calls at 4pm. **Setting `profile` to a transport mode** instead of a profile name. **Underestimating service time.** The dominant source of schedule failure. **Blocking an HTTP request on a synchronous solve.** **Leaving `costs` at example values.** `fixed` is how you say "do not use a twelfth technician for one job." **Full re-solve for a single emergency insertion.** ## Alternatives — honestly **Google Maps Platform** has no equivalent to Tour Planning. For the optimization core it is not a candidate. Its Distance Matrix could feed a solver you build yourself. **A dedicated field service optimization vendor** — Skedulo, Descartes, and similar — is the right answer if scheduling *is* your product's core value and you need multi-day, preference-weighted, or SLA-penalty-aware objectives that Tour Planning's cost model does not express. Feed such a solver with [Matrix Routing](/guides/matrix-routing); the matrix is the interface. **Your own solver** (OR-Tools, Jsprit) is legitimate when the objective function is genuinely custom. It is not legitimate as a cost-avoidance measure — Tour Planning is included in the Base Plan, and a self-maintained VRP solver becomes a product nobody is funded to own. **Manual dispatch** genuinely wins below roughly 15 jobs a day. A dispatcher who knows the territory beats a solver you have not tuned. Introduce optimization when the dispatcher becomes the bottleneck, not when the board asks about AI. ## Related guides Skills, time windows, priorities, and the async solve lifecycle. The cost table, and the interface to any solver you bring yourself. When geographic assignment beats dynamic assignment. Caching customer addresses, which is most of your bill. Also: [Fleet Routing](/use-cases/fleet-routing) · [Routing](/guides/routing) · [Catchment Area](/guides/catchment-area) ## HERE documentation * [Tour Planning API](https://docs.here.com/tour-planning/docs/introduction-tour-planning) * [Tour Planning quick start](https://docs.here.com/tour-planning/docs/quick-start) * [Matrix Routing API v8](https://www.here.com/docs/category/matrix-routing-api-v8) ## Placematic * [UpGrid — territory management](https://placematic.com/territory-management/) * [HERE Location Services](https://placematic.com/here-location-services/) *** See the packaged solution for your industry: [Field Services](https://placematic.com/solutions/field-services/) Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate costs, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Fleet Electrification Feasibility Source: https://docs.placematic.com/use-cases/fleet-electrification Answering whether your existing routes can be run by EVs — the analysis you do before buying trucks, and why most of it needs no routing API at all. # Fleet Electrification Feasibility This page is about a question asked before any vehicle is purchased: **can our existing routes be run by electric vehicles?** It is an analytical question, not a dispatch question. The distinction matters, because the analytical version is far cheaper to answer than most teams assume. ## The problem A fleet operator has 400 routes, run daily, by diesel vehicles. Someone — a customer, a regulator, a board — asks about electrification. The questions that follow: * Which routes fit within an EV's range today? * Which fit if we charge mid-route, and where would that be? * What does charging time do to the schedule? * Which depots need charging infrastructure, and how much? * What breaks first: range, charging time, or charger availability? None of these require dispatching a vehicle. They require analysing history against vehicle specifications. Do not buy an EV routing API to answer a feasibility question. Historical route distances from your telematics, compared against vehicle range and duty cycle, answers most of it. Buy the API when you need to *dispatch*, not when you need to *analyze*. ## Who this is for Fleet operators evaluating electrification. Sustainability and operations teams. Consultancies producing feasibility studies. Logistics companies assessing Class 8 electrification. Anyone who has been asked for a number by a board. ## Recommended architecture ```mermaid theme={null} flowchart TB subgraph Hist["What you already have"] TRIPS[(Historical trips
distance, duration, dwell)] DEPOT[Depot locations] DUTY[Duty cycles
shift length, load] end subgraph Tier1["Tier 1 — free, answers most of it"] RANGE[Range comparison
trip distance vs vehicle range] SPLIT[Feasible / marginal / infeasible] end subgraph Tier2["Tier 2 — where an API earns its place"] ISO[Consumption isolines
reachability on a charge] CP[EV Charge Points
corridor coverage] EVR[EV Routing
marginal routes only] end subgraph Out["Output"] SCORE[Route-level feasibility] INFRA[Depot charging requirement] GAP[Corridor charging gaps] end TRIPS --> RANGE --> SPLIT DUTY --> RANGE SPLIT -->|feasible| SCORE SPLIT -->|infeasible| SCORE SPLIT -->|marginal| EVR DEPOT --> ISO ISO --> INFRA CP --> GAP EVR --> SCORE ``` **Tier 1 costs nothing and classifies most routes.** Only the marginal band justifies API calls. ## Tier 1: the analysis you already have data for Take your historical trips. For each, compute total daily distance per vehicle per shift. Compare against a candidate EV's usable range, derated for: * **Temperature.** Cold weather reduces usable range materially. * **Load.** A fully laden vehicle consumes more. * **Auxiliary load.** Refrigeration on a delivery van in July is not negligible. * **Terrain.** Sustained grade dominates consumption. * **Depth-of-discharge policy.** Nobody runs a battery to zero. Classify: * **Feasible** — comfortably within derated range. Electrify. * **Marginal** — within range under favourable conditions only. Requires analysis. * **Infeasible** — exceeds range with no realistic charging stop. Not today. In most fleets this classification alone answers 70–80% of the question at zero API cost. Present that first. It buys credibility for the harder analysis on the marginal band, and it may be all anyone needed. The derating factors are where the honesty lives. A linear consumption assumption is optimistic on highways and catastrophically optimistic on grades. ## Tier 2: relevant HERE APIs, and why **[Catchment Area](/guides/catchment-area) with consumption range type** — reachability on a charge. **Why:** "everywhere this vehicle can reach on a full battery, from this depot" is a reachability polygon with `consumption` as the range type, not time or distance. It answers the depot-siting question directly: which of our stops fall inside, which fall outside. **HERE EV Charge Points API** — corridor coverage. **Why:** station locations, connector types, power feeds. It is a **separate product** from EV routing, at [v2 and v3](https://www.here.com/docs/category/ev-charge-points-api-v3), with a documented migration path and a separate entitlement. **[EV Routing](/guides/ev-routing)** — the marginal band only. **Why:** where a route requires a mid-route charge, the vehicle's ability to reach charger three depends on whether it stopped at charger one. That is a routing problem and cannot be approximated by filtering a route against a charger table. **[Matrix Routing](/guides/matrix-routing)** — matrix responses can include consumption alongside time and distance. Useful for scoring many candidate routes at once. EV routing and EV Charge Points are different APIs with different entitlements and separate bills. A key that routes EVs may return `403` from the charge points endpoint. Confirm both before you architect. See [Getting a HERE API Key](/getting-started/getting-a-here-api-key). ## The commercial vehicle problem **Truck charging is not car charging, and this is not a solved problem in the market.** HERE documents EV truck charging locations as a distinct concern within the EV Charge Points product. Connector types, power levels, and physical site access differ from passenger charging. A Class 8 vehicle routed against passenger charging infrastructure is routed to sites it cannot physically enter — no pull-through, no clearance, no capacity. Charging corridors for commercial vehicles are sparse. Any feasibility study for heavy trucks that treats the passenger charging network as available infrastructure is producing a number that will not survive contact with a driver. Be honest with whoever commissioned the study about what the data supports. ## Implementation flow 1. **Extract historical trips.** Distance, duration, dwell time, shift boundaries, per vehicle. 2. **Derate range** by temperature, load, auxiliary draw, terrain, and depth-of-discharge policy. Document every assumption. 3. **Classify** feasible / marginal / infeasible. 4. **For depot siting**, compute consumption isolines from each depot. Which stops fall inside? 5. **For the marginal band only**, run EV routing with a proper consumption curve and realistic starting charge. 6. **Overlay charger network** to identify corridor gaps. 7. **Model charging time as schedule time.** A 45-minute charge is 45 minutes the vehicle is not delivering. 8. **Present ranges, not point estimates.** The uncertainty is real; hiding it is not. ## Cost considerations **Tier 1 is free.** Do it before anything else. **Restrict Tier 2 to the marginal band.** Running EV routing across 400 routes when 280 are trivially feasible is waste. **Cache the charger network aggressively.** Station locations change over months. Availability changes by the minute — but availability does not matter for a feasibility study, which is analysing a hypothetical future. Do not query it. **Batch corridor analysis.** Nothing is waiting. **Consumption isolines are bounded by depot count.** A 12-depot network is 12 calls, refreshed when vehicle specifications change. **Do not query charge points on map interaction.** Every pan becomes a request. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Common mistakes **Buying an EV routing API to answer a feasibility question.** Range comparison against history costs nothing. **Linear consumption models.** Highways and grades break them. **Ignoring auxiliary load.** Refrigerated vans, in summer, with the doors opening forty times. **Ignoring temperature derating.** Winter range is not summer range. **Assuming full charge at shift start.** Depot charging capacity constrains this. **Filtering a route against a charger table.** The vehicle's ability to reach charger three depends on charger one. **Routing commercial EVs against passenger charging data.** Inaccessible sites. **Treating charging time as free.** It is schedule time and it is vehicle downtime. **Presenting a point estimate.** "62% of routes are electrifiable" implies precision the analysis does not carry. **Confusing EV routing with the EV Charge Points API.** Separate products, separate entitlements. **Analysing routes rather than duty cycles.** A vehicle runs several routes a day. Range constrains the day, not the route. ## Production checklist * [ ] Historical trip data extracted at the duty-cycle level, not the route level * [ ] Range derating documented: temperature, load, auxiliary, terrain, depth of discharge * [ ] Tier 1 classification completed and presented before any API spend * [ ] Consumption curve used, not a linear approximation * [ ] Starting charge modelled from depot charging capacity, not assumed full * [ ] EV routing applied to the marginal band only * [ ] Commercial-vehicle charging infrastructure distinguished from passenger * [ ] Charging time modelled as vehicle downtime in the schedule * [ ] Both EV Routing and EV Charge Points entitlements confirmed * [ ] Results presented as ranges with stated assumptions ## Alternatives and trade-offs **No routing API at all.** For most feasibility studies this is the correct answer. Telematics history plus vehicle specifications plus a spreadsheet. Spend the money on better derating assumptions rather than better routing. **Google Maps Platform** has limited EV routing with charge-state awareness. For consumer navigation the gap has narrowed. For commercial fleet electrification with truck-specific charging, it is not competitive. **Charging network operator APIs** — ChargePoint, Electrify America — give authoritative data for *their* network. Better than any aggregator for that network. Worse for coverage. For a fleet standardizing on one network, go direct. **Open Charge Map** is free and community-maintained. Coverage and freshness vary. Adequate for a coverage map or a prototype. Not adequate for dispatching a vehicle with 40 km of remaining range — but feasibility studies are not dispatch. **A consultancy or OEM feasibility service.** Vehicle manufacturers publish consumption curves and will run this analysis. If you are buying their vehicles, the analysis is often free and better parameterized than anything you will assemble. **Wait.** Charging infrastructure for commercial vehicles is expanding. A route that is infeasible today may be feasible in eighteen months without any change to your fleet. The output of this analysis should include *which corridors, if built, unlock the most routes* — that is a more actionable finding than a percentage. ## Related guides Consumption models, charge state, and the two-product distinction. The dispatch-time architecture, once feasibility is established. Consumption-range isolines for depot siting. Where electrified routes eventually have to be dispatched. Also: [Matrix Routing](/guides/matrix-routing) · [Getting a HERE API Key](/getting-started/getting-a-here-api-key) ## HERE documentation * [HERE EV Charge Points API v3](https://www.here.com/docs/category/ev-charge-points-api-v3) * [Routing API v8 reference](https://docs.here.com/routing/reference/routing-api-v8-calculateroutes) — EV routing parameters * [Matrix Routing API v8](https://www.here.com/docs/category/matrix-routing-api-v8) ## Placematic * [EV Routing](https://placematic.com/here-location-services/ev-routing/) *** Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Building a Fleet Routing Platform Source: https://docs.placematic.com/use-cases/fleet-routing Architecture for fleet routing systems on HERE — separating dispatch planning from device guidance, where the constraint set lives, and the cost structure that decides whether you survive scale. # Building a Fleet Routing Platform ## The business problem You dispatch commercial vehicles. Every route you produce must be physically drivable by the vehicle assigned to it, legally permissible for its cargo, and completable within a driver's hours. Get this wrong and the failure is not a bad UX review. It is a bridge strike, a citation, or a driver reversing a 53-foot trailer down a residential street at 6am. ## Typical users Fleet tech and ELD vendors. Trucking and LTL carriers. Last-mile delivery platforms. Field operations software. Anyone whose product tells a professional driver where to go. ## Recommended architecture ```mermaid theme={null} flowchart TB subgraph Data["System of record"] VEH[Vehicle profiles
height, grossWeight, axleCount, hazmat] JOBS[Jobs / stops] CACHE[(Geocode cache)] end subgraph Plan["Planning tier — scheduled"] MATRIX[Matrix Routing
cost table] TOUR[Tour Planning
sequence + assignment] end subgraph Exec["Execution tier — on demand"] ROUTE[Routing v8
leg geometry + ETA] DEVICE[Driver app
on-device guidance] end subgraph Post["Reconstruction — batch"] MATCH[Route Matching
driven segments] RG[Batch reverse geocode
stop labels] end VEH --> MATRIX VEH --> TOUR VEH --> ROUTE VEH -->|profile push| DEVICE JOBS --> CACHE CACHE --> MATRIX MATRIX --> TOUR TOUR --> ROUTE ROUTE --> DEVICE DEVICE -->|GPS trace| MATCH MATCH --> RG ``` Four tiers, four different latency and cost profiles. Collapsing them is the mistake this whole page exists to prevent. ## Which HERE APIs, and why **[Matrix Routing](/guides/matrix-routing)** — the cost table. Travel times between depots and stops. **Why:** because the alternative is a loop over the Routing API, and a 20×500 problem is 10,000 routing calls or one matrix request. This is the largest single cost lever in fleet software. **[Tour Planning](/guides/tour-planning)** — the assignment and sequence. Which vehicle, which stops, what order. **Why:** the Vehicle Routing Problem is NP-hard. You are not solving it with nearest-neighbour, and HERE's solver handles capacity, time windows, mixed fleets, and priorities as first-class constraints. It is included in the Base Plan. **[Truck Routing](/guides/truck-routing)** — the leg geometry. `transportMode=truck` with explicit physical constraints. **Why:** because `transportMode=truck` alone selects the engine and describes nothing about your vehicle. Height in centimetres, `grossWeight` in kilograms, `axleCount` including trailers. **[Route Matching](/guides/route-matching)** — reconstruction. Which road segments the vehicle actually drove. **Why:** IFTA, hours-of-service, and speed compliance require the *travelled* segment, not the nearest address. [Reverse geocoding](/guides/reverse-geocoding) cannot answer this and will not survive an audit. **[Reverse Geocoding](/guides/reverse-geocoding)** — human-readable stop labels. **Why:** dispatchers read addresses, not coordinates. Use `POST /multi-revgeocode` on stop detection, not on every ping. **[HERE SDK](/guides/here-sdk)** — only if drivers need offline maps or on-device turn-by-turn. Otherwise tiles plus REST. ## Implementation flow 1. **Model the vehicle.** Height, gross weight, axle count, trailer count, hazmat class, category. This is a database table, not a request parameter. 2. **Geocode and cache stops.** Once. Addresses do not move. See [Batch Geocoding](/guides/batch-geocoding). 3. **Build the matrix** for the dispatch window's depots and stops. 4. **Solve** with Tour Planning. Async. Persist the `statusId`. 5. **Handle unassigned jobs.** The solver will tell you which stops it could not serve. Surface them. 6. **Compute leg geometry** with truck routing for each assigned leg. 7. **Push the vehicle profile to the device**, alongside the route. 8. **Ingest GPS.** Do not geocode it. Do not match it. Store it. 9. **On trip close**, segment, downsample, and match. Batch. Overnight. Step 7 is where fleet platforms fail catastrophically. The server computes a constrained truck route. The driver deviates. The phone recalculates — using whatever constraints reached the app. If the profile never left your backend, the device just routed a 4.1m trailer under a 3.4m bridge, in real time, with turn-by-turn audio. ## Data flow **Vehicle profile** flows from your database into every routing call *and* onto the device. One source. No hardcoded dimensions at call sites. **Stops** flow through the geocode cache before they reach any routing API. A stop that was geocoded last week does not get geocoded again. **GPS** flows in at a fixed rate and does nothing expensive on arrival. It is stored. Trip segmentation, matching, and labelling happen later, in bulk. ## Production considerations **Separate planning from execution.** Planning is a scheduled job measured in seconds or minutes. Execution is a request measured in milliseconds. A dispatcher clicking "optimize" should not block on a 90-second solve. **Solve on a schedule, not on every event.** Re-solving when each new order arrives produces route churn. Drivers stop trusting an app whose plan changes hourly. **Mid-day replanning is a new problem.** Current vehicle location becomes the shift start; completed jobs are removed. Fresh submission, not an incremental update. **Compliance couples to routing.** For ELD platforms, the route determines hours-of-service feasibility. If the route changes, HOS projections change. Model that dependency explicitly rather than discovering it in an audit. **Record the map release version** on anything a regulator might read. A trip matched in March against a March map may match differently in September. **Test on trap geometry, in CI.** Route a 410cm vehicle through the 11foot8 bridge in Durham NC, Storrow Drive in Boston, and the Southern State Parkway on Long Island. Any path returned is a failure. Run the same three in `car` mode as a control. Wire those three assertions into your test suite, not your QA checklist. A refactor that drops `vehicle[height]` from a params dict is invisible in code review and obvious in a failing test. ## Scaling **The matrix is the bottleneck, and it is a cache.** Depot locations do not move. Hash the input set. A depot-to-stop matrix recomputed nightly for an unchanged depot network is pure spend. **Solve time grows with problem size, not linearly.** `configuration.termination.maxTime` is the knob. The documentation example sets it to 2 seconds. On a 200-stop problem that produces a legal solution a dispatcher will reject. Find your knee empirically. **Partition by depot before you partition by anything else.** Fleet-wide optimization across geographically independent depots buys nothing and costs solve time. **GPS volume is the quiet scaling problem.** 200 vehicles at a 10-second ping rate is 648,000 points per day. Any per-point API call — geocoding, matching, positioning — multiplies by that number. Design so that nothing expensive touches a raw ping. ## Cost optimization Ranked by impact, from real fleet deployments: 1. **Stop reverse-geocoding every GPS ping.** Geocode detected stops. Ratio of geocode calls to detected stops should be near 1. 2. **Replace routing loops with Matrix Routing.** Count the routing calls that exist only to populate a distance table. 3. **Cache geocoding permanently.** Depots, customer sites, and stops resolve once. 4. **Batch trip matching overnight.** Nothing is waiting. 5. **Segment trips before matching.** Drop idle periods. In real telematics feeds this removes 40–60% of submitted points. 6. **Set `return` explicitly** on routing calls. Nothing consumes `instructions` server-side. 7. **Hash the tour problem.** Skip re-solves that would produce the same answer. Fleet operators with a countable vehicle fleet and unpredictable call volume should evaluate **asset-based pricing** against call-volume pricing. A dispatcher who reroutes 200 trucks forty times a day is punished by call-volume pricing for operational diligence. See [HERE Pricing Explained](/getting-started/here-pricing-explained). Asset-based pricing availability depends on contract tier. Confirm it before it becomes load-bearing in a business case presented to your CFO. ## Common mistakes **Approximating Tour Planning with N routing calls.** Slower, more expensive, worse routes. **Looping Routing to build a matrix.** The same mistake, one layer down. **Vehicle constraints hardcoded at call sites.** They belong on the vehicle record. **Constraints on the server, absent on the device.** The recalculation path is the dangerous one. **Reverse-geocoding GPS packets.** Geocode events. **Using reverse geocoding to reconstruct driven routes.** That is Route Matching, and only Route Matching survives an audit. **Leaving `maxTime: 2` from HERE's example.** Legal solution, unusable sequence. **Ignoring unassigned jobs.** The solver told you it could not serve them. **Blocking an HTTP request on a synchronous solve.** **Passing metres where centimetres are expected.** A four-centimetre truck routes anywhere. ## Alternatives — honestly **Google Maps Platform** added Large Vehicle Routing in August 2026 — dimensions, gross weight, axle count and hazmat classes, for the 48 contiguous United States. For a US-domestic fleet running online dispatch it is now a real option and you should evaluate it rather than dismiss it. What it does not do, per Google's own documentation: truck toll prices, speed limits, radioactive cargo. No coverage beyond the contiguous 48. No offline navigation. And it has no equivalent to Route Matching, which means no defensible jurisdiction-mile reconstruction. If your platform crosses a border, quotes freight, or answers to an auditor, those are the reasons — not the absence of a truck profile. Keep Google if your product also does consumer place discovery. **Mapbox** is strong on styling and developer experience, weak on truck attributes. If your fleet is passenger vehicles and your differentiator is the map's appearance, Mapbox deserves evaluation. **TomTom** supports truck constraints with a narrower set and lower waypoint limits. Competitive in European automotive. Verify current pricing independently. **OSRM** is free and yours to operate. That means truck attributes, traffic, and map freshness are also yours to operate. For a well-funded team with GIS expertise this can work. For most fleet vendors it becomes a second product nobody is paid to maintain. **Build your own solver** fed by Matrix Routing if you need custom objectives beyond distance, time, and fixed vehicle cost. Legitimate. Do not build one to avoid a licensing conversation that does not exist — Tour Planning is in the Base Plan. ## Related guides Constraints, units, and the trap geometry that proves they apply. The solver, the async lifecycle, and unassigned jobs. The cost table underneath everything above. Reconstruction for IFTA, HOS, and speed compliance. Also: [Reverse Geocoding](/guides/reverse-geocoding) · [HERE SDK](/guides/here-sdk) · [HERE Pricing Explained](/getting-started/here-pricing-explained) ## HERE documentation * [Routing API v8](https://www.here.com/docs/category/routing-api-v8) * [Matrix Routing API v8](https://www.here.com/docs/category/matrix-routing-api-v8) * [Tour Planning API](https://docs.here.com/tour-planning/docs/introduction-tour-planning) ## Placematic * [HERE Location Services](https://placematic.com/here-location-services/) * [Truck Routing](https://placematic.com/here-location-services/truck-routing/) * [HERE API pricing](https://placematic.com/here-location-services/here-pricing/) *** See the packaged solution for your industry: [Fleet & ELD](https://placematic.com/solutions/fleet-eld/) Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate costs, migrate from Google Maps and build production-ready geospatial systems. We have deployed HERE into production ELD platforms. [Talk to us](https://placematic.com/contact/). # Geofencing at Scale Source: https://docs.placematic.com/use-cases/geofencing Geofence entry and exit detection across thousands of zones and vehicles — a spatial index problem, not an API problem, and the two cases where that stops being true. # Geofencing at Scale Start with the conclusion, because it will save you a procurement cycle. **Geofencing is a spatial index problem. You do not need a mapping API to solve it.** A PostGIS `ST_Contains` against an indexed polygon column, executed in your own database, answers the question in sub-milliseconds and costs nothing per call. Placematic sells HERE. This page still says: do not buy an API for this. ## The problem Ten thousand vehicles. Two thousand zones — depots, customer sites, restricted areas, delivery districts. Positions arriving every ten seconds. You must know, reliably, when a vehicle enters or leaves each zone. Calling a mapping API on every position, for every zone, to answer a containment question is not merely expensive. It is the wrong operation. Containment is a geometry test. It does not require a road network, traffic data, or a routing engine. The naive version fires a network call per packet per zone. Ten thousand vehicles × 8,640 daily packets × 2,000 zones is a number nobody should compute twice. ## Who this is for Fleet and telematics engineers. IoT platform teams. Logistics and delivery platforms. Anyone with a `zones` table and a `positions` stream. ## Recommended architecture ```mermaid theme={null} flowchart TB subgraph Zones["Zone definition — where an API may help"] DRAW[Hand-drawn polygons
from ops] ISO[Drive-time isolines
HERE, quarterly] BUF[Radius buffers] end subgraph Store["Spatial store"] PG[(PostGIS
GIST index on geometry)] end subgraph Detect["Detection — no API, ever"] PKT[Position packet] LAST[Previous state
per vehicle × zone] CONT[ST_Contains] TRANS{State changed?} EVT[Emit enter / exit event] end DRAW --> PG ISO --> PG BUF --> PG PKT --> CONT PG --> CONT CONT --> TRANS LAST --> TRANS TRANS -->|yes| EVT TRANS -->|no| DROP[Discard] ``` The only box that touches HERE is zone *definition*, and only when the zone is a drive-time polygon. ## Where HERE is genuinely relevant Two cases. Both are about defining the zone, never about testing it. **Drive-time zones.** "Everywhere within 20 minutes of this depot" is a reachability polygon, not a circle. A radius around a Chicago facility includes several miles of Lake Michigan. Compute the isoline once, store the polygon, test against it forever. See [Catchment Area](/guides/catchment-area). **Labelling the event.** A vehicle entered a zone at a coordinate. If a human will read "arrived at 1400 Industrial Pkwy," you need [reverse geocoding](/guides/reverse-geocoding) — once, on the event, not on the packet. Everything else is your database. ## Relevant HERE APIs, and why **[Catchment Area](/guides/catchment-area)** — drive-time zone geometry. **Why:** road networks, rivers, and limited-access highways shape reachability. Circles do not know this. Computed quarterly, materialized, then never called again. **[Reverse Geocoding](/guides/reverse-geocoding)** — labelling entry and exit events for humans. **Why:** dispatchers read addresses. One call per event, not per packet. **Not for containment. Ever.** If your architecture calls an external service to answer "is this point inside this polygon," you have built a spatial database with an API bill attached. Materialize the polygons. ## Implementation flow 1. **Define zones.** Hand-drawn by ops, radius buffers, or drive-time isolines — depending on what the zone means. 2. **Store as geometry in PostGIS**, with a `GIST` index. This is the entire performance story. 3. **Maintain per-vehicle-per-zone state.** Inside or outside. This is what makes it a transition detector rather than a containment checker. 4. **On each packet, test containment** against candidate zones only. 5. **Compare to previous state.** Emit an event only on change. 6. **Debounce transitions.** GPS jitter at a zone boundary produces enter/exit/enter/exit within seconds. Require N consecutive readings, or a minimum dwell. 7. **Label the event** with reverse geocoding, if a human reads it. ## Making it fast **Bound the candidate set before testing.** Do not test 2,000 zones per packet. Use the spatial index: `ST_DWithin` against a bounding radius, or a coarse H3 cell lookup, to narrow to the handful of zones plausibly nearby. Then test those. **Partition zones by region.** A vehicle in Illinois cannot be inside a Texas zone. **Keep state in memory.** Per-vehicle-per-zone inside/outside flags belong in Redis or an in-process map, not in a table you query per packet. **Simplify polygons.** A 40,000-vertex isoline is slower to test than a 400-vertex simplification, and it was an approximation with a resolution parameter to begin with. **Debounce at the boundary.** This is a correctness fix as much as a performance one. Undebounced, a truck idling on a zone edge generates hundreds of spurious events. Boundary jitter is the defect that gets geofencing systems distrusted. A driver parked at a depot gate triggers forty arrival notifications. Operations turns the feature off. Require dwell, or require consecutive readings, before emitting. ## Cost considerations **The detection path should cost zero.** If it does not, the architecture is wrong. **Zone definition cost is bounded by zone count.** A 400-site network with three drive-time bands is 1,200 isoline calls, quarterly. Not per vehicle. Not per day. **Event labelling cost is bounded by events.** A truck making twelve stops generates twelve reverse-geocode calls, not 8,640. **Do not use a geocoding API to test whether an address is in a zone.** Geocode the address once, store the point, run `ST_Contains`. This is the same principle as [Delivery Zones](/use-cases/delivery-zones). **PostGIS containment against an indexed geometry column scales with your traffic for free.** That is the whole point. ## Common mistakes **Calling any API for containment.** The mistake this page exists to prevent. **Testing every zone against every packet.** Bound the candidate set first. **No `GIST` index on the geometry column.** Sequential scan over polygons. **No transition state.** You now have a containment checker that fires continuously while a vehicle sits inside a zone. **No debouncing at boundaries.** Spurious events, distrusted feature. **Radius buffers where drive time was meant.** Lakes, rivers, highways with no exit. **Reverse-geocoding the packet rather than the event.** **Storing zones as arrays of coordinates rather than as geometry.** You have opted out of spatial indexing. **Running containment in application code.** The database has an index. Use it. **Un-simplified isoline polygons.** Thousands of vertices, tested millions of times. ## Production checklist * [ ] Zones stored as PostGIS geometry with a `GIST` index * [ ] Candidate zone set bounded before containment testing * [ ] Per-vehicle-per-zone state maintained in memory * [ ] Events emitted on transition only, never on containment * [ ] Boundary debouncing implemented — dwell time or consecutive readings * [ ] Polygons simplified before storage * [ ] Zero external API calls in the detection path * [ ] Drive-time zones computed as isolines, materialized, refreshed on a schedule * [ ] Event labelling — reverse geocoding — fires per event, not per packet * [ ] Zones versioned and effective-dated, if disputes are possible ## Alternatives and trade-offs **PostGIS is the answer for most teams.** Mature, free, indexed, and it runs where your data already lives. This page is largely an argument for it. **Managed geofencing services** — AWS Location Service, HERE Tracking, Google's Geofencing API on Android — handle transition detection, debouncing, and event delivery. Worth evaluating if you have no spatial database and no appetite to run one. You are paying for the state machine and the plumbing, not for the geometry test. Read the pricing model carefully: some bill per position evaluated, which reintroduces the problem this page describes. **On-device geofencing** — iOS and Android both support it natively, with radio-assisted transition detection that consumes less battery than continuous GPS. If your geofences are few, static, and known to the device, this is strictly better than server-side detection: lower latency, lower battery, zero backend cost. It does not scale to two thousand zones per device. **H3 or S2 cell indexing** instead of polygon containment. Precompute the set of cells covering each zone. Detection becomes a hash lookup on the vehicle's current cell. Extremely fast, approximate at boundaries, and the approximation is tunable by resolution. A good fit when you have very many zones and tolerate boundary fuzziness. **Do you need geofences at all?** Some teams build zone detection to answer questions that stop detection already answers. If the event you care about is "the vehicle arrived at a customer," stop detection plus a nearest-site lookup may be simpler and more robust than maintaining polygon geometry for every customer. ## Related guides Drive-time polygons — the one place an API earns its place here. The ingest architecture this detection layer sits inside. The same materialization pattern, applied to serviceability. Labelling events, once, for the humans who read them. Also: [Fleet Routing](/use-cases/fleet-routing) · [Reducing Google Maps Costs](/use-cases/reducing-google-maps-costs) ## HERE documentation * [Routing API v8](https://www.here.com/docs/category/routing-api-v8) — isoline routing * [Geocoding & Search v7](https://www.here.com/docs/category/geocoding-search-api-v7) *** Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial systems — including telling you when you do not need one. [Talk to us](https://placematic.com/contact/). # Hazmat and Restricted Cargo Routing Source: https://docs.placematic.com/use-cases/hazmat-routing Routing hazardous materials — class-based restrictions, tunnel categories, and why a route that returns 200 can still be a federal violation. # Hazmat and Restricted Cargo Routing A routing API will return a route for a hazmat load whether or not you told it the load is hazmat. The response will be well-formed. It will contain a polyline. It may also route a tanker of flammable liquid through a tunnel where that is prohibited, past a school, or onto a bridge with a placarded-vehicle restriction. **HERE will not warn you, because you did not tell it what you were carrying.** ## The problem Hazardous materials routing is governed by regulation, not preference. In the United States, placarded vehicles are subject to federal and state routing designations. In Europe, ADR classes determine tunnel access. Local restrictions layer on top. The consequences of a wrong route are not customer dissatisfaction. They are: * Federal and state penalties * Loss of hazmat endorsement * Criminal liability in the event of an incident * Insurance exclusion This is one of a small number of use cases where a software defect is a regulatory event. If your platform produces routes for placarded vehicles, the hazmat class is not an optional parameter. Omitting it does not produce an error. It produces a route computed for an unconstrained vehicle. ## Who this is for Chemical and industrial logistics. Fuel and propane distribution. Waste and hazardous waste haulers. Explosives and munitions transport. Any TMS or ELD platform serving carriers with hazmat endorsements. ## Recommended architecture ```mermaid theme={null} flowchart TB subgraph Load["The load defines the constraint"] MAT[Material] CLASS[Hazmat class] PLACARD[Placarding requirement] end subgraph Vehicle["The vehicle defines the rest"] DIM[Height, grossWeight,
axleCount] TUNNEL[Tunnel category] ENDORSE[Driver endorsement] end subgraph Route["Routing"] VALIDATE{Class + vehicle
complete?} TR[Truck Routing
with shippedHazardousGoods] CHECK[Assert route returned] end subgraph Audit["Record"] REC[(Class, constraints,
route, timestamp,
map release)] end MAT --> CLASS --> VALIDATE PLACARD --> VALIDATE DIM --> VALIDATE TUNNEL --> VALIDATE VALIDATE -->|no| FAIL[Refuse to route] VALIDATE -->|yes| TR --> CHECK --> REC ENDORSE --> VALIDATE ``` **The system must be able to refuse.** A routing request for a hazmat load without a class is not a request to route with defaults. It is an invalid request. ## Relevant HERE APIs, and why **[Truck Routing](/guides/truck-routing)** — the only relevant API. **Why:** HERE's Routing v8 exposes `shippedHazardousGoods` as a parameter with class values, alongside tunnel category and the physical vehicle constraints. `shippedHazardousGoods` is an **array** of cargo type values, not a boolean and not a single class. HERE's specification defines: `explosive`, `gas`, `flammable`, `combustible`, `organic`, `poison`, `radioactive`, `corrosive`, `poisonousInhalation`, `harmfulToWater`, `other`. `tunnelCategory` is a **separate parameter**, taking the ADR tunnel restriction code `B`, `C`, `D` or `E`. It governs which tunnels the vehicle may enter. It is not a hazmat class. A single shipment may carry several cargo types and one tunnel category. Do not map an internal `is_hazmat` boolean onto either. Read the definitions in the [Routing API v8 reference](https://docs.here.com/routing/reference/routing-api-v8-calculateroutes) and map your material classifications deliberately. **Physical constraints still apply.** Height in centimetres, `grossWeight` in kilograms, `axleCount` including trailers. A hazmat tanker is also a tall, heavy vehicle. Both constraint sets are required. **`category=lightTruck` is a legal exemption, not a physical one.** It exempts a vehicle from many restrictions written for normal trucks. Physical dimension and cargo restrictions still apply. It does not exempt a hazmat load from hazmat restrictions. **`weightLimit` is deprecated** in the current v8 specification in favour of `grossWeight`. The entire `truck` object is also deprecated in favour of `vehicle`. If you are reading an older tutorial, you are reading deprecated parameters. ## Implementation flow 1. **Classify the material** in your own system, against the regulatory scheme that applies in your jurisdiction. 2. **Map your classification to HERE's enum** deliberately, with a documented mapping table reviewed by someone who understands the regulation. 3. **Validate before routing.** A load flagged hazmat with no class must fail loudly. Never default. 4. **Route with the full constraint set** — hazmat class, tunnel category, and physical dimensions. 5. **Assert a route was returned.** HERE returns `200` with an empty `routes` array and a `notice` when no path exists. For hazmat, "no legal route" is a real and correct outcome. 6. **Record the constraint set, the route, the timestamp, and the map release version.** 7. **Verify the driver's endorsement** matches the load. This is not a routing API's job, and it is your job. An empty `routes` array on a hazmat request is not a bug. It may be the correct answer: no permissible route exists between these points for this class. A system that retries with relaxed constraints, or falls back to car routing, has just produced an illegal route. ## Production considerations **Constraints belong to the load, not the carrier.** The same tractor hauls dry van on Monday and hazmat class D on Tuesday. `shippedHazardousGoods` is a property of what is in the trailer, and it changes per shipment. **Vehicle dimensions belong to the vehicle record.** Height, weight, and axle count are not request-time literals. **Never hardcode either at a call site.** A developer adding a new endpoint who copies a routing call without the constraint set has introduced a defect invisible to code review. **Record the map release version.** Restrictions change. A route computed in March against a March map may differ from the same route in September. If an incident occurs, you will be asked what the system knew and when. **Regulatory data is not uniform globally.** Coverage and depth of hazmat restriction data vary by market. Do not assume the same fidelity in every country you operate. **Verify against known restrictions in your operating area.** Build a test suite of routes that must be refused or diverted, and run it in CI. Ask your local regulator or carrier compliance officer for a handful of known-prohibited segments in your operating region. Assert that a routed hazmat vehicle avoids each. This is the hazmat equivalent of trap-geometry testing for truck height, and it is worth more than any amount of documentation review. ## Cost considerations Hazmat routing bills as a routing transaction. It is not more expensive than truck routing. **This is not a cost-optimization page.** The economics are irrelevant next to the regulatory exposure. Cache route geometry between fixed points if you like. Do not cache across different hazmat classes, do not reuse a route computed for a different load, and do not let a caching layer serve a class-A route to a class-D shipment because the origin and destination matched. Cache keys for hazmat routes must include the full constraint set. An origin-destination cache key is a defect. For matrices of hazmat-constrained travel times, [Matrix Routing](/guides/matrix-routing) accepts truck mode and vehicle parameters. ## Common mistakes **Setting `transportMode=truck` and omitting `shippedHazardousGoods`.** The engine is selected. The cargo is not declared. **Mapping an internal `is_hazmat` boolean onto `shippedHazardousGoods`**. It is an array of cargo types. Read the definitions. **Confusing `shippedHazardousGoods` with `tunnelCategory`**. Cargo type and tunnel restriction code are different parameters governing different things. Supplying one does not imply the other. **Using the deprecated `weightLimit`** instead of `grossWeight`. **Omitting tunnel category.** A separate parameter, and the one that governs tunnel access in Europe. **Assuming `lightTruck` waives hazmat restrictions.** It waives certain legal truck restrictions. It does not make a flammable liquid non-flammable. **Treating an empty `routes` array as an error to retry.** It may be the correct answer. **Falling back to car routing when truck routing returns no path.** This is how illegal routes reach drivers. **Caching on origin-destination without the constraint set.** **Constraints attached to the carrier rather than the load.** **Not recording the map release version** on a route a regulator may examine. **Not verifying driver endorsement.** Not the API's job. Still your problem. ## Production checklist * [ ] Material classification mapped to HERE's `shippedHazardousGoods` enum, with a documented mapping table * [ ] Mapping table reviewed by someone qualified in the applicable regulation * [ ] Routing request validated: hazmat load without a class fails loudly, never defaults * [ ] Tunnel category supplied where applicable * [ ] Physical vehicle constraints supplied alongside hazmat class * [ ] `grossWeight` used, not the deprecated `weightLimit` * [ ] Empty `routes` array handled as a legitimate outcome, not retried with relaxed constraints * [ ] No fallback to car routing under any circumstance * [ ] Cache keys include the full constraint set * [ ] Constraint set stored on the load; vehicle dimensions on the vehicle * [ ] Route, constraints, timestamp, and map release version persisted * [ ] CI assertions against known-prohibited segments in your operating region * [ ] Driver endorsement verified against load class before dispatch ## Alternatives and trade-offs **Google Maps Platform** added hazardous goods classification to Large Vehicle Routing in August 2026, for the 48 contiguous United States. Its documentation lists routing for radioactive materials as unsupported, and it disclaims reliance — the response can flag that restrictions were partially ignored, and drivers are told not to rely solely on the route being safe or legal. For a US domestic placarded haul outside class 7, it is now a candidate worth evaluating. For radioactive cargo, cross-border movement, or any operation where the route is the compliance artifact, the disclaimer alone should decide it. Verify the class mapping against Google's current reference rather than assuming parity with HERE's eleven cargo types. **Mapbox** does not express hazmat constraints. **TomTom** supports ADR HazMat classes with a narrower constraint set than HERE and lower waypoint limits. Competitive in European automotive contexts. Verify current capability and pricing independently. **Self-hosted OSRM** does not model hazmat restrictions. You would be curating restriction data yourself, for a domain where being wrong is a regulatory event. This is not a reasonable build. **Manual route approval.** For low-volume, high-consequence hauls — explosives, radioactive materials, oversized chemical loads — a compliance officer reviewing every route is not a failure of automation. It is the correct control. Automate the routing; keep the human in the approval path. **A specialist hazmat routing vendor** exists in some markets, with jurisdiction-specific designated-route data that exceeds any general mapping platform. If hazmat is your entire business, evaluate them. HERE gives you strong coverage as part of a general routing platform; a specialist gives you depth in one regulatory regime. **Refuse to route.** The most underrated option. If your system cannot confidently express a load's constraints, it should decline rather than guess. A dispatcher calling a compliance officer is a better outcome than a driver following an illegal route. ## Related guides Physical constraints, units, and trap-geometry validation. Compliance architecture, audit artifacts, and map release versioning. Where the constraint set has to reach the driver's device. Constraints belong to the load, not the carrier. Also: [Routing](/guides/routing) · [Matrix Routing](/guides/matrix-routing) · [Cold Chain and Restricted Cargo](/use-cases/logistics-platform) ## HERE documentation * [Routing API v8 reference](https://docs.here.com/routing/reference/routing-api-v8-calculateroutes) — `shippedHazardousGoods`, tunnel category, truck parameters * [Transport modes](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/topics/transport-modes.html) ## Placematic * [Truck Routing](https://placematic.com/here-location-services/truck-routing/) *** See the packaged solution for your industry: [Logistics & Delivery](https://placematic.com/solutions/logistics/) Need help designing or implementing a production HERE solution? Placematic is an official HERE reseller and implementation partner. We help engineering teams select the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Last-Mile Delivery Optimization Source: https://docs.placematic.com/use-cases/last-mile-delivery Dense urban delivery — the walking segment routing ignores, failed attempts as a first-class outcome, and why the optimizer's plan and the driver's day diverge. # Last-Mile Delivery Optimization Last mile is the expensive mile, and the reason is not routing. The van reaches the street in fifteen minutes. Then the driver spends eleven minutes finding parking, four minutes walking to the door, three minutes waiting, and leaves a card because nobody answered. **Routing solved fifteen minutes of that. Eighteen minutes were somewhere else.** ## The problem Multi-stop urban delivery has properties that general routing does not model: * **Parking is not free and not modelled.** The van stops somewhere near the address, not at it. * **The walking segment is real.** Apartment blocks, gated complexes, pedestrian-only streets, the fourth floor. * **Service time varies enormously** and dominates travel time in dense routes. * **Failed attempts are normal**, not exceptional. Re-delivery is a cost centre and a routing problem in its own right. * **Time windows are constraints, not preferences.** * **Vehicle access restrictions** apply — weight limits, height limits, delivery hour restrictions on certain streets. A route that is optimal for a moving vehicle can be miserable for a person carrying parcels. ## Who this is for Parcel and courier platforms. Grocery and meal delivery. Pharmacy and healthcare logistics. E-commerce fulfilment operations. Anyone whose driver leaves the vehicle. ## Recommended architecture ```mermaid theme={null} flowchart TB subgraph Prep["Preparation"] ADDR[Addresses] GC[Geocode + cache
with confidence score] ACCESS[Access metadata
your data] end subgraph Plan["Planning — nightly"] MX[Matrix Routing
stops × stops] TP[Tour Planning
windows, capacity, priority] UNASSIGNED[Unassigned stops
surfaced to ops] end subgraph Exec["Execution"] RT[Routing
leg geometry] DRIVER[Driver app] ATTEMPT{Delivery
attempt} end subgraph Fail["Failure path — design it"] RETRY[Re-attempt queue] REPLAN[Next-day insertion] end ADDR --> GC --> MX ACCESS --> TP MX --> TP TP --> UNASSIGNED TP --> RT --> DRIVER --> ATTEMPT ATTEMPT -->|failed| RETRY --> REPLAN --> TP ``` The failure path is not an edge case. In dense residential delivery, first-attempt failure is a routine outcome. A system that models it as an exception will accumulate re-deliveries in a spreadsheet. ## Relevant HERE APIs, and why **[Tour Planning](/guides/tour-planning)** — the sequence. **Why:** last mile *is* the Vehicle Routing Problem with time windows and capacity. HERE's solver supports capacitated VRP, time windows, priorities, pickup-and-delivery, and reloads as first-class variants. It is included in the HERE Base Plan. Service time is `duration` on the task. Time windows are `times`. Priorities protect high-value stops from being dropped. Model these properly and the solver does its job. **[Matrix Routing](/guides/matrix-routing)** — the cost table underneath. **Why:** a 120-stop route requires travel times between all stops. That is one matrix, not thousands of routing calls. **[Routing](/guides/routing)** — leg geometry for the driver. Choose the transport mode that matches the vehicle. `bicycle` and `scooter` transport modes carry beta status with limited functionality in HERE Routing v8. Validate them for your market before building courier ETAs on them. See [HERE's transport modes reference](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/topics/transport-modes.html). **[Truck Routing](/guides/truck-routing)** — if the vehicle exceeds light-commercial class, or if urban weight and height restrictions apply. HERE exposes a `lightTruck` category that exempts a vehicle from many legal truck restrictions while still applying physical dimension limits. Check whether your fleet qualifies before paying the routing penalty. **[Geocoding](/guides/geocoding)** — with confidence scores. **Why:** a low-confidence geocode in last mile is a failed delivery. See [Address Validation](/use-cases/address-validation). ## What the API cannot give you This is the section that matters, and it is entirely your data: **Access metadata.** Buzzer code, floor, gate, loading dock, "leave with concierge." Collect it, store it against the address, and feed it to the driver. **Service time by address type.** A ground-floor house is not a fourteenth-floor apartment. Instrument actuals and estimate per type, not per stop. **Parking difficulty.** No routing API models it. Your own dwell-time history does. **Failure history.** An address that failed three times last month should be flagged before the fourth attempt. Instrument actual service time per address, per attempt. Within a quarter you will have a better predictor than any generic model, and the schedules produced from it will be believable to drivers. ## Implementation flow 1. **Geocode and validate addresses**, storing the confidence score. Route low-confidence addresses to review before the van leaves. 2. **Attach access metadata** to the address record. 3. **Build the matrix** for the day's stop set. 4. **Solve with Tour Planning**, async. Set `configuration.termination.maxTime` to something appropriate for your problem size — the documentation example sets it to 2 seconds, which produces a legal solution a dispatcher will reject on a 120-stop route. 5. **Handle unassigned stops explicitly.** Capacity, windows, and shift time will make some stops unservable. Surface them before the day starts, not at 4pm. 6. **Compute leg geometry** for the assigned sequence. 7. **Push route, access metadata, and service-time expectations** to the driver app. 8. **Capture the attempt outcome.** Delivered, failed, refused — with a reason. 9. **Feed failures into the next day's problem.** With elevated priority if the SLA demands it. ## Cost considerations **Solve nightly, not on every order.** Re-optimizing when each order arrives produces route churn drivers will not follow. A stable, near-optimal schedule outperforms a perfect one nobody trusts. **Hash the problem.** If the stop set, fleet, and constraints are unchanged, reuse the solution. **Matrix caches well.** Depot locations are fixed. Delivery addresses in a dense zone repeat across days. **Cache geocoding permanently.** A repeat customer costs nothing. See [Address Validation](/use-cases/address-validation). **Do not re-route on every GPS ping** to refresh the driver's ETA. Compute the remaining duration from route geometry. See [ETA Calculation](/use-cases/eta-calculation). **Set `costs` deliberately in Tour Planning.** `fixed` cost per vehicle tells the solver whether adding a van is cheap or expensive. That is a business decision. Most teams leave it at the example value and wonder why the solution uses every vehicle. Tour Planning is in the Base Plan. Your cost is dominated by geocoding volume, matrix calls, and solve frequency. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Common mistakes **Approximating Tour Planning with N routing calls.** Slower, more expensive, worse routes. **Looping Routing to build the matrix.** **Leaving `maxTime: 2` from HERE's documentation example.** **Setting `profile` to a transport mode** rather than a name referencing `fleet.profiles`. This trips up nearly every first Tour Planning integration. **Ignoring unassigned stops.** The solver told you it could not serve them. **Modelling failed delivery as an exception.** **Uniform service time across address types.** The largest error term in the schedule. **Ignoring access metadata.** The driver stands outside a gate the system did not know about. **Re-optimizing on every event.** Drivers route around a schedule they cannot trust. **Low-confidence geocodes dispatched without review.** A failed delivery you paid to attempt. **Car routing for a scooter or bicycle courier.** Forbidden turns, illegal segments. In dense markets this is a safety issue. **Assuming beta transport modes are production-ready.** ## Production checklist * [ ] Geocode confidence score stored; low-confidence addresses routed to review pre-dispatch * [ ] Access metadata modelled and delivered to the driver app * [ ] Service time instrumented per address type and refreshed from actuals * [ ] Matrix used for the cost table; no routing loops * [ ] Tour Planning submitted async; `statusId` persisted before polling * [ ] `maxTime` and `stagnationTime` tuned against real problem size * [ ] `costs.fixed` set to reflect actual vehicle economics * [ ] Unassigned stops surfaced to operations before the shift begins * [ ] Failed attempts captured with a reason, and fed into next-day planning * [ ] Transport mode matches the actual vehicle; beta modes validated * [ ] Vehicle constraints applied where urban restrictions exist * [ ] Solve runs on a schedule; mid-day replanning treated as a new problem ## Alternatives and trade-offs **Google Maps Platform** has no equivalent to Tour Planning. Its Distance Matrix can feed a solver you build. It cannot express commercial vehicle constraints, which matters in cities with weight and access restrictions. **A dedicated last-mile platform** — Onfleet, Bringg, Shipsy — solves dispatch, driver app, proof of delivery, and the failure workflow. If last mile is your operation rather than your product, buy it. The routing is not the hard part; the workflow is. **Your own solver** — OR-Tools, Jsprit — is legitimate when your objective function is genuinely custom: multi-day, driver-preference-weighted, or SLA-penalty-aware in ways Tour Planning's cost model does not express. Feed it a cached matrix; the matrix is the interface. It is not legitimate as cost avoidance, since Tour Planning is in the Base Plan. **Manual sequencing** genuinely wins below roughly fifteen stops per driver, especially with an experienced driver who knows the territory. Introduce optimization when the dispatcher becomes the bottleneck. **Do not optimize the wrong thing.** Before investing in a solver, instrument where the time actually goes. If travel is 30% of the day and service plus failed attempts are 70%, a 10% improvement in routing buys you 3%. Access metadata, parking data, and attempt-success prediction may be worth more than any optimizer. ## Related guides Time windows, capacity, priorities, and unassigned jobs. The cost table, and the interface to any solver you bring. A low-confidence geocode is a failed delivery you paid for. Service time dominates. Model it separately from travel. Also: [Restaurant Delivery](/use-cases/restaurant-delivery) · [Field Service](/use-cases/field-service) · [Truck Routing](/guides/truck-routing) ## HERE documentation * [Tour Planning API](https://docs.here.com/tour-planning/docs/introduction-tour-planning) * [Tour Planning quick start](https://docs.here.com/tour-planning/docs/quick-start) * [Transport modes](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/topics/transport-modes.html) *** See the packaged solution for your industry: [Logistics & Delivery](https://placematic.com/solutions/logistics/) Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Location Intelligence for Business Analytics Source: https://docs.placematic.com/use-cases/location-intelligence Building a location intelligence capability — the spatial join that dominates the workload, why the mapping API is a small line item, and when you do not need one at all. # Location Intelligence for Business Analytics ## The business problem Most business data has a location attached and almost none of it is used spatially. Every order has a delivery address. Every customer has a ZIP. Every store has a coordinate. Joining those to demographics, competitors, drive-time reachability, and each other produces answers that a BI tool cannot reach. The engineering reality: **this is a data pipeline with a mapping API attached, and the API is the smallest part.** ## Typical users Data and analytics teams. Business intelligence functions. Commercial strategy. Retail and CPG analytics. Real estate research. Anyone whose dashboard has a map on it and should have more. ## Recommended architecture ```mermaid theme={null} flowchart TB subgraph Ingest["Ingest"] BIZ[(Business data
orders, customers, stores)] BATCH[Batch geocode
deduplicated] end subgraph Enrich["Enrichment — external"] DEMO[Demographics
ACS, households] POI[Competitor / POI data
from a data vendor] REACH[Isolines + Matrix
reachability] end subgraph Core["Spatial core — where the work is"] PG[(PostGIS
indexed geometry)] GRID[H3 or census units] JOIN[Spatial joins] end subgraph Serve["Serve"] BI[BI tools] TILES[Vector tiles
for maps] API[Internal spatial API] end BIZ --> BATCH --> PG DEMO --> GRID POI --> PG REACH --> PG GRID --> JOIN PG --> JOIN --> BI JOIN --> TILES JOIN --> API ``` ## Which HERE APIs, and why **[Batch Geocoding](/guides/batch-geocoding)** — the entry point, and usually the largest single API cost. **Why:** your business data is addresses. Spatial analysis needs coordinates. Nothing is waiting; batch it, deduplicate first. **[Catchment Area](/guides/catchment-area)** — reachability as an analytical dimension. **Why:** "revenue within a 15-minute drive of each store" is a question a radius cannot answer honestly. **[Matrix Routing](/guides/matrix-routing)** — drive-time relationships at scale. **Why:** distance-decayed models, accessibility scoring, network analysis. Straight-line distance is a poor proxy and you already know it. **[Maps](/guides/maps)** — rendering. Tiles into Leaflet, OpenLayers, or MapLibre GL. **Do not use a mapping API's place index as your competitor or POI dataset.** Coverage, freshness, and categorization are not built for analytics. Buy POI data from a data vendor, or source it deliberately. This is the most common quiet failure in location intelligence programmes: an analysis whose conclusions rest on a place database that was never fit for the purpose. **And frequently, no routing API at all.** See Alternatives. ## Implementation flow 1. **Deduplicate and normalize addresses.** A raw order export contains the same address hundreds of times. 2. **Batch geocode once.** Persist coordinates, normalized address, confidence score, and `geocoded_at`. 3. **Never geocode that address again.** New addresses arrive at a trickle and are geocoded in real time. 4. **Choose an aggregation grid.** H3 resolution 7–8, or census units if you need to join published demographics directly. 5. **Join business data to the grid.** This is a PostGIS problem. 6. **Enrich with demographics.** ACS at the tract or block-group level. 7. **Add reachability where it earns its place.** Isolines for coverage, matrix for distance decay. 8. **Serve.** Vector tiles for maps, aggregated tables for BI. ## Data flow Geocoding is **once, forever, cached**. It is a property of an address, not of a query. Reachability is **materialized**. Store polygons and matrices. A dashboard that computes drive time on filter change is a dashboard nobody uses twice. The spatial join is **the workload**. Millions of points against thousands of polygons, indexed. This is database engineering, and it is where your effort actually goes. Before you evaluate a mapping vendor, instrument how much of your intended analysis is answered by a spatial join over data you already have. For many teams the answer is most of it, and the routing API is a small addendum rather than the foundation. ## Production considerations **Store the confidence score.** A low-confidence geocode persisted as truth silently corrupts every downstream aggregation. Surface it. Threshold it. Do not let a rooftop match and a city-centroid fallback carry equal weight in a revenue map. **Geocoding quality is a data quality problem, not an API problem.** Bad input addresses produce bad coordinates on any platform. Normalize first. **Demographic vintage matters.** ACS 5-year estimates carry margins of error that most dashboards silently discard. If you present a household count to three significant figures, you are overstating what you know. **Grid choice constrains everything.** H3 gives you uniform cells and easy aggregation. Census units give you free demographic joins and irregular geometry. You will regret whichever you choose, differently. **Rendering large geometries is its own problem.** Millions of points do not go into a browser. Vector tiles, server-side aggregation, or both. **MapLibre GL `property/stops` expressions require integer values.** A `numeric` type from PostGIS causes silent rendering failure — no error, no styling. Cast it. **Version your enrichment.** A revenue analysis run against 2024 demographics and re-run against 2026 will disagree, and someone will need to know why. ## Scaling **Geocoding is bounded by distinct addresses**, not by records. A million orders from 80,000 customers is 80,000 geocodes, once. **The spatial join scales with grid resolution.** H3 resolution 9 nationally is an enormous number of cells for marginal analytical gain. Coarsen aggressively; refine locally. **Isoline and matrix costs are bounded by location count.** Stores, depots, and facilities. Business numbers you know. **Tile generation is a batch job.** Pregenerate. Do not render on request. **Query performance is an indexing problem.** `GIST` indexes on geometry, and think about whether your join is point-in-polygon or nearest-neighbour before you write it. ## Cost optimization 1. **Deduplicate before batch geocoding.** Order exports repeat addresses enormously. 2. **Geocode once, cache permanently**, keyed on normalized address. 3. **Materialize reachability.** No isolines in a dashboard. 4. **Coarsen the grid.** Resolution 7 answers most questions resolution 9 answers, at a fraction of the join cost. 5. **Pregenerate tiles.** 6. **Buy POI data.** Do not pay per-request for a place index that was never the right source. The mapping API bill for a mature location intelligence programme should be **small and flat**: a one-time geocoding backfill, a trickle of new addresses, and a quarterly reachability refresh. If it is large and variable, something in the pipeline is calling an API where it should be reading a table. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Common mistakes **Geocoding in the analytics query.** Geocode at ingest. **Not deduplicating before batch.** **Discarding the confidence score.** **Using a mapping place index as a competitor dataset.** **Computing isolines interactively in a dashboard.** **H3 resolution 9 when resolution 7 would answer the question.** **Presenting ACS estimates without their margins of error.** **Rendering raw points to the browser.** **Silent MapLibre styling failure** from a `numeric` PostGIS column where an integer was required. **Building a routing integration to answer a question a spatial join already answers.** ## Alternatives — honestly **Many location intelligence programmes need no routing API.** If your questions are "revenue by ZIP," "customers within our footprint," or "which cells are underserved," those are spatial joins. PostGIS, a geocoder, and demographic data. Buy the routing API when drive time genuinely changes the answer — and be able to say when it does. **Google Maps Platform** is competitive for geocoding and superior for place data. It does not expose isoline polygons in an equivalent form. For a programme dominated by geocoding and rendering, with no drive-time requirement, the platform choice is close to arbitrary — decide on cost and on whatever else you already license. **Esri** owns this space at the high end. ArcGIS, Business Analyst, and their demographic products are more capable than what you will assemble. If location intelligence is a discipline in your firm rather than a project, evaluate Esri seriously before building. **Open data plus DuckDB Spatial, Overture Maps, and OSM** is a genuinely viable modern stack. Free geocoding is worse; free everything else is often good enough. For an analytics team with SQL skills and no real-time requirement, this is a defensible build that costs engineering rather than licence fees. **Placematic UpInsight** is a spatial analytics platform for business teams making location decisions — territory performance, trade area measurement, site scoring. It sits between "build it on PostGIS" and "buy Esri." Evaluate it as the middle option it is: less capable than Esri, faster to value than a build, and appropriate when the analytical questions are commercial rather than cartographic. The honest framing: **the mapping vendor is rarely the decision that determines whether a location intelligence programme succeeds.** Data quality, grid choice, and whether anyone acts on the output determine that. ## Related guides The entry point, and usually your largest API line item. The decision this analytical layer feeds. Reachability as an analytical dimension, materialized. Where spatial analysis meets organizational reality. Also: [Geocoding and Search](/guides/geocoding) · [Matrix Routing](/guides/matrix-routing) · [Points of Interest](/guides/points-of-interest) ## HERE documentation * [Batch API v7](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/batch-api-quick-start.html) * [Geocoding & Search v7](https://www.here.com/docs/category/geocoding-search-api-v7) * [Matrix Routing API v8](https://www.here.com/docs/category/matrix-routing-api-v8) ## Placematic * [UpInsight — spatial analytics](https://placematic.com/spatial-data-analytics/) * [UpGrid — territory management](https://placematic.com/territory-management/) *** Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate costs, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Architecture for Logistics Platforms and TMS Source: https://docs.placematic.com/use-cases/logistics-platform Building a TMS on HERE — the multi-tenant constraint problem, planning versus execution tiers, freight-specific data flows, and where a routing API stops being enough. # Architecture for Logistics Platforms and TMS ## The business problem A TMS does not route one fleet. It routes many, for many customers, each with different vehicles, different cargo classes, different service commitments, and different tolerance for being told a load cannot be delivered today. The engineering difficulty is not routing. It is that **every constraint is per-tenant**, and the system must be correct for all of them simultaneously. ## Typical users TMS vendors. Freight brokerages with software. 3PL platforms. Shipper-side transportation software. Multi-carrier logistics SaaS. ## Recommended architecture ```mermaid theme={null} flowchart TB subgraph Tenant["Per-tenant configuration"] FLEET[Fleet definitions] RULES[Service rules
windows, SLAs] RATES[Rate tables] end subgraph Core["Shared spatial core"] GC[Geocode cache
tenant-isolated] MX[Matrix Routing] TP[Tour Planning] RT[Routing v8] end subgraph Ops["Operations"] DISP[Dispatch] TRACK[Track & trace] POD[Proof of delivery] end subgraph Settle["Settlement & compliance"] RM[Route Matching] IFTA[Jurisdiction miles] BILL[Distance-based billing] end FLEET --> MX FLEET --> TP RULES --> TP GC --> MX MX --> TP TP --> DISP DISP --> RT RT --> TRACK TRACK --> POD TRACK -->|GPS| RM RM --> IFTA RM --> BILL RATES --> BILL ``` ## Which HERE APIs, and why **[Matrix Routing](/guides/matrix-routing)** — lane cost tables. **Why:** a TMS answers "what would this lane cost across every carrier in the network." That is a matrix question asked at planning time, cached, and reused across quotes. **[Tour Planning](/guides/tour-planning)** — consolidation and load building. **Why:** multi-stop, multi-vehicle, capacitated, with time windows. Pickup-and-delivery VRP is a first-class supported variant. **[Truck Routing](/guides/truck-routing)** — the executed leg. **Why:** physical constraints vary per tenant and per load. A hazmat class D shipment routes differently from dry van, on the same lane, for the same carrier. **[Route Matching](/guides/route-matching)** — settlement and compliance. **Why:** distance-based billing and IFTA jurisdiction miles both require the *driven* segments. Reverse geocoding cannot produce them and will not survive an audit. This is the API that turns telematics into money. **[Batch Geocoding](/guides/batch-geocoding)** — onboarding. **Why:** a new tenant arrives with 40,000 shipper and consignee addresses. Nothing is waiting. Batch it. **[Catchment Area](/guides/catchment-area)** — service coverage. **Why:** "which terminals can serve this ZIP within our SLA" is a reachability question, materialized once, queried locally. ## Implementation flow 1. **Isolate tenants at the data layer** before anything else. Vehicle profiles, geocode cache, and matrices are all tenant-scoped. A cross-tenant cache hit is a data breach. 2. **Onboard addresses via batch geocode**, deduplicated. A raw shipper export contains enormous repetition. 3. **Materialize terminal catchments.** Store polygons. Serviceability becomes `ST_Contains`, not an API call. 4. **Build lane matrices** for the tenant's active depot–destination sets. Cache aggressively; lanes are stable. 5. **Solve load building** with Tour Planning, per tenant, per dispatch window. 6. **Route executed legs** with the load's specific truck constraints — not the carrier's default profile. 7. **Ingest telematics.** Store. Nothing expensive on arrival. 8. **Match trips overnight.** Attribute segments to jurisdictions. Feed settlement. ## Data flow The critical property: **spatial computation is shared infrastructure; constraints are tenant data.** The geocode cache is shared *code*, isolated *data*. The matrix service is shared *code*, tenant-keyed *cache*. Tour Planning is called with tenant-specific fleet and rules. The tempting optimization — one global geocode cache, because addresses are addresses — is where multi-tenant spatial systems leak. Tenant A's customer list is inferable from cache hit patterns. Isolate the cache. The savings were never worth it. ## Production considerations **Quotes are not dispatch.** A rate quote needs a plausible distance in 200ms. A dispatch needs a constrained, drivable route. Different latency budgets, different APIs, different acceptable error. Do not serve quotes from the routing engine. **Cache lane distances for quoting.** Chicago to Dallas for a 53' dry van does not change between Tuesday and Wednesday. Compute once, serve from a table. **Constraints belong to the load, not the carrier.** The same tractor hauls dry van on Monday and hazmat on Tuesday. `shippedHazardousGoods` is a property of what is in the trailer. **Settlement disputes are why you keep the raw trace.** Matched output is a derivation. When a carrier disputes billed miles, you need the observations and the map release version that produced the match. **Rate limits are shared across your tenants.** One tenant's bulk operation can `429` another tenant's dispatch. Queue and prioritize at your layer. HERE sees one contract. Give each tenant a call budget in your own system, enforced before the request leaves your infrastructure. Without it, your largest customer degrades service for everyone and you find out from a support ticket. ## Scaling **Lane matrix cache is the highest-leverage store in the system.** Hit rates above 90% are achievable because freight lanes repeat. **Solve per tenant, per depot region.** Global optimization across tenants is meaningless and expensive. **Trip matching is the volume problem.** Every tracked vehicle across every tenant generates a trace. Segment, downsample, batch. Anything per-point multiplies by fleet size times ping rate times tenant count. **Onboarding is bursty.** A large tenant arrives with millions of addresses. Batch API concurrency limits are per-contract. Queue onboarding jobs; do not let them starve nightly enrichment. ## Cost optimization 1. **Lane distance cache for quoting.** Quotes vastly outnumber dispatches. 2. **Materialized terminal catchments.** Serviceability checks are free in your own database. 3. **Batch geocode all onboarding**, deduplicated first. 4. **Matrix, never routing loops**, for lane cost tables. 5. **Trip segmentation before matching.** Drop idle time. 6. **Per-tenant call budgets.** Prevents one tenant's inefficiency from becoming your invoice. 7. **Deduplicate the input to everything.** Shipper address exports repeat constantly. For a platform with a countable tracked-asset population, evaluate **asset-based pricing** against call-volume. A TMS billing per shipment but paying per API call has a margin structure that inverts under load. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Common mistakes **A shared geocode cache across tenants.** A leak, not an optimization. **Serving rate quotes from the routing API.** Cache lane distances. **Constraints attached to the carrier rather than the load.** **No per-tenant rate budget.** One tenant `429`s everyone. **Reverse geocoding to compute jurisdiction miles.** That is Route Matching. **Discarding the raw trace after matching.** Settlement disputes need it. **Not recording the map release version** on billed distances. **Onboarding jobs competing with nightly enrichment** for batch concurrency. **Assuming one carrier profile per tenant.** Fleets are heterogeneous. ## Alternatives — honestly **Google Maps Platform** cannot express the physical and legal constraints a TMS must respect. It also has no equivalent to Route Matching, which means no defensible jurisdiction miles. For the routing and settlement core, it is not a candidate. It remains better for any consumer-facing address autocomplete you expose to shippers. **Mapbox** is a reasonable choice for the *visualization* layer if your product's differentiator is its interface. The routing and compliance core stays on HERE. **OSRM plus OSM** is used by TMS vendors with serious GIS teams. You inherit truck attribute curation, traffic, and map freshness as ongoing engineering. Realistic only if location is your core competency, not your infrastructure. **A commercial optimization vendor** (rather than Tour Planning) makes sense if your objective function is genuinely custom — multi-day, cross-dock, or driver-preference-weighted. Feed it with [Matrix Routing](/guides/matrix-routing). The matrix is the interface. ## Related guides The API that turns telematics into billable distance. Load building, pickup-and-delivery, and unassigned jobs. Lane cost tables and the cache that makes quoting cheap. Tenant onboarding at millions of addresses. Also: [Fleet Routing](/use-cases/fleet-routing) · [Truck Routing](/guides/truck-routing) · [Catchment Area](/guides/catchment-area) ## HERE documentation * [Matrix Routing API v8](https://www.here.com/docs/category/matrix-routing-api-v8) * [Tour Planning API](https://docs.here.com/tour-planning/docs/introduction-tour-planning) * [Batch API v7](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/batch-api-quick-start.html) ## Placematic * [Maps for Transport and Logistics](https://placematic.com/here-location-services/maps-for-transport-and-logistics-here/) * [HERE Location Services](https://placematic.com/here-location-services/) *** See the packaged solution for your industry: [Logistics & Delivery](https://placematic.com/solutions/logistics/) Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate costs, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Reducing Google Maps API Costs Source: https://docs.placematic.com/use-cases/reducing-google-maps-costs What actually drives a Google Maps bill, which fixes work without changing vendors, and how to model whether migration is worth the engineering. # Reducing Google Maps API Costs Before you evaluate a vendor, evaluate your call pattern. A meaningful share of teams who set out to migrate discover, after instrumenting, that the bill halves without changing platforms. What remains after that is your real migration case — and now you have a clean baseline to measure it against. Migrating an uncached, undebounced implementation to a cheaper provider moves the waste to a cheaper meter. You will save money and still be wrong. ## The problem The invoice arrived and someone in finance drew a line under it. The reflex is to compare per-1,000 rates across vendors. They are close at entry level. That comparison teaches you nothing, because your bill is not determined by the rate card. It is determined by: * Whether you cache geocoding results * Whether you batch what tolerates latency * Whether you debounce autocomplete * Whether you loop routing calls to build distance tables * Whether you reverse-geocode GPS pings on ingest * Which volume tier you land in * Which free bundles you consume, and whether you noticed Six of those seven are yours to fix, today, on your current platform. ## Who this is for CTOs and engineering managers who just received a cost escalation. Product engineers asked to "look into Maps spend." Anyone building a migration business case they will have to defend. ## Recommended approach ```mermaid theme={null} flowchart TB A[1. Instrument
per-endpoint calls from logs] --> B[2. Fix call patterns
on Google] B --> C[3. Re-measure] C --> D{Remaining bill
material?} D -->|No| E[Done. You saved money
without a migration.] D -->|Yes| F[4. Model HERE cost
at same call volume] F --> G[5. Estimate migration
engineering cost] G --> H{Payback period
< 18 months?} H -->|No| I[Say so. The argument
is weaker than it looks.] H -->|Yes| J[6. Migrate one surface.
Stabilize. Repeat.] ``` Steps 1 through 3 are free and nobody does them first. ## Step 1: Instrument Not estimates. Logs. Count calls per endpoint per month. Separate: * Geocoding — real-time vs. what could be batch * Reverse geocoding — user-facing vs. telematics ingest * Routing — user-facing vs. calls that populate a distance table * Autocomplete — sessions vs. keystrokes * Tiles — with and without a CDN in front The output of this step is a table. Without it, everything downstream is guessing, including the vendor comparison. ## Step 2: Fix the call pattern Ranked by impact. These apply on any platform. **Cache geocoding results permanently.** Addresses do not move. Re-geocoding the same customer address on every order is the single largest avoidable cost in this domain. Store normalized address, coordinates, confidence score, and a timestamp against the customer record. Key the cache on the *normalized* address so `123 Main St` and `123 Main Street` hit the same entry. **Stop reverse-geocoding GPS pings on ingest.** A vehicle emitting a position every ten seconds over a nine-hour shift produces 3,240 pings. Across 200 vehicles that is 648,000 per day. The address is needed when a human looks at a screen or when a stop is detected. Not on packet arrival. Instrument the ratio of reverse-geocoding calls to detected stops. Anything meaningfully above 1 means you are geocoding pings. **Debounce autocomplete.** It fires on keystrokes. Undebounced, you bill once per character typed, per session, forever. 200–300ms. **Batch what tolerates latency.** Nightly address normalization, historical backfill, bulk imports. If the result is written to a database rather than rendered to a screen, it should have been batched. Batch endpoints are cheaper per record. **Deduplicate before batching.** A raw export of order addresses contains enormous repetition. Geocoding 4 million rows that contain 900,000 distinct addresses bills for 4 million. **Replace routing loops with matrix operations.** If you call a point-to-point routing API in a loop to build a table of travel times, count those calls. A 200×3,000 problem is 600,000 routing calls, or one matrix request. See [Matrix Routing](/guides/matrix-routing). **Put a CDN in front of tiles.** Tiles are static per zoom, x, y, and style. This is available to you regardless of vendor. **Set `return` fields explicitly.** Requesting full turn-by-turn instructions and polylines when you needed a duration inflates payloads. Nothing consumes `instructions` server-side. Do all of the above first. Re-measure. Present *that* number to finance. You will have already delivered a result, and the migration conversation becomes optional rather than defensive. ## Step 3: Model the migration honestly **The meters do not map one to one.** Google bills per session where HERE bills per request, and the reverse. You cannot derive a HERE forecast from a Google invoice. This is why step 1 exists. | Line | Source | | -------------------------------------------- | ----------------------------------------- | | Current spend, per endpoint | Logs | | Savings from steps above, on Google | Measured, after | | Remaining spend — the migration target | Derived | | Projected HERE cost at same call volume | Modelled under both pricing models | | Migration engineering cost | Estimated honestly | | Which Google SKU your truck traffic lands on | Console — LVR does not bill at Essentials | | Payback period | Derived | Teams migrating at production volume typically see meaningful savings, with geocoding-heavy workloads showing the largest gap. Those figures assume the call pattern is already sane. **If the payback period exceeds eighteen months, say so before your CFO does.** It costs you one migration and buys you credibility for the ones that are worth doing. ## Where HERE genuinely changes the economics Not everywhere. Three places: **Matrix operations.** If you loop routing calls, the gap is not a rate difference. It is an architectural one. **Batch geocoding at volume.** Cheaper per record, and the workload is usually enormous. **Truck routing — and this became a cost argument in August 2026.** On HERE a truck route bills as a routing request: same rate, same free allowance as a car route. Google's Large Vehicle Routing requires the traffic-aware optimal routing preference, which bills above its Essentials rate. Describing your vehicle now costs more per call on Google and nothing extra on HERE. It remains a capability argument as well — toll cost, speed limits and coverage beyond the contiguous 48 are documented gaps — but the meter difference is real and belongs in the model above. **Contractual price stability** matters to teams who lived through vendor repricing. HERE pricing through a Gold Partner is contractual and does not reprice mid-term. ## Cost considerations Two commercial models exist and choosing wrong is expensive: **Call volume pricing** — per API call. Suits SaaS platforms whose call volume tracks customer count. **Asset-based pricing** — per tracked asset per month. Suits fleet operators with a countable vehicle fleet and unpredictable call volume. A dispatcher who reroutes 200 trucks forty times a day is punished by call-volume pricing for operational diligence. Asset-based pricing availability depends on contract tier. Confirm it before it becomes load-bearing in a business case presented to your CFO. Free bundles are per-API and do not pool. Exhausting your routing bundle does not consume your geocoding bundle. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Common mistakes **Comparing list prices instead of workload cost.** Entry rates are close. Call mix, tier, batching, and bundles determine your bill. **Migrating before optimizing.** You may be migrating waste. **Presenting savings without migration cost.** Your CFO will ask. **Assuming the meters map.** Session-based and request-based billing are not translatable. **Optimizing the wrong endpoint.** Instrument first. The thing you assume is expensive usually isn't. **Treating a free bundle as an architectural guarantee.** It is a marketing boundary. **Migrating everything at once.** Two systems change, one incident occurs, root cause is ambiguous. **Cutting a feature to save money.** Cheaper on a feature people stop using is not savings. ## Production checklist * [ ] Per-endpoint call counts pulled from logs, not estimated * [ ] Geocoding cache in place, keyed on normalized address, with hit rate instrumented * [ ] Reverse-geocode-to-stop-detection ratio measured * [ ] Autocomplete debounced at 200–300ms * [ ] Batch pipeline for latency-tolerant geocoding, with input deduplicated * [ ] Routing loops identified and counted * [ ] CDN in front of tiles * [ ] `return` fields explicit on every routing call * [ ] Re-measured after all of the above * [ ] Migration engineering cost estimated, payback period computed and stated ## Alternatives and trade-offs **Stay on Google.** If your spend is under roughly \$1,000/month after optimization, the payback period on migration engineering is measured in years. Do something else. If your product's core value is consumer place discovery — business hours, reviews, photos — Google's data is categorically better and you should keep it regardless of cost. **Hybrid.** Keep Google for consumer place search and autocomplete. Move routing, matrix operations, and batch geocoding to HERE. This is a common and defensible endpoint. Document it as a decision before someone calls it a partial migration. **Self-host OSRM.** Free, and yours to operate. Truck attributes, traffic, and map freshness become your engineering. Realistic for a team with GIS expertise where location is core competency, not infrastructure. **Mapbox.** Strong on styling and developer experience. Weak on commercial vehicle depth. If your differentiator is the map's appearance rather than routing correctness, it deserves evaluation. **Do nothing.** If the bill is 0.4% of revenue and growing linearly with a business that is growing faster, this is not the highest-value thing your engineers could be doing. Say that out loud. ## Related guides Sequencing, shadow-writing, and validating quality during cutover. Call volume versus asset-based, and modelling a bill you can defend. The routing loop that is costing you money right now. The first surface to migrate. Smallest risk, largest saving. Also: [Geocoding and Search](/guides/geocoding) · [Vehicle Tracking](/use-cases/vehicle-tracking) · [HERE vs Google Maps](/comparisons/here-vs-google-maps) ## Placematic * [HERE API pricing](https://placematic.com/here-location-services/here-pricing/) * [Cost calculator](https://placematic.com/tools/calculator) *** Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Architecture for Restaurant Delivery Platforms Source: https://docs.placematic.com/use-cases/restaurant-delivery Building restaurant delivery on HERE — the serviceability check that must never call an API, courier assignment, dynamic delivery pricing, and where Google is the better choice. # Architecture for Restaurant Delivery Platforms ## The business problem Three questions, three latency budgets, three different correct answers. **"Do you deliver here?"** — at checkout, in milliseconds, on every session. **"Which courier takes this order?"** — at dispatch, in seconds, on every order. **"What does delivery cost?"** — at checkout, derived from the first answer. Conflating them produces a platform whose checkout calls a routing API and whose margin evaporates at scale. ## Typical users Restaurant delivery marketplaces. Ghost kitchen platforms. Multi-brand restaurant groups running their own delivery. POS vendors adding delivery. ## Recommended architecture ```mermaid theme={null} flowchart TB subgraph Cold["Offline — quarterly"] REST[Restaurant locations] ISO[Isolines by band] PG[(PostGIS zones)] end subgraph Hot["Checkout — sub-100ms"] ADDR[Address entry] AC[Autocomplete] GC[Geocode + cache] CONT[ST_Contains] PRICE[Band → fee] end subgraph Dispatch["Order — seconds"] MX[Matrix Routing
couriers × orders] ASSIGN[Assignment] RT[Routing v8
courier ETA] end subgraph Track["Live"] GPS[Courier GPS] ETA[ETA refresh] end REST --> ISO --> PG ADDR --> AC --> GC --> CONT PG --> CONT --> PRICE CONT -->|serviceable| MX --> ASSIGN --> RT GPS --> ETA RT --> ETA ``` ## Which HERE APIs, and why **[Catchment Area](/guides/catchment-area)** — delivery zones, materialized. **Why:** serviceability is asked on every session. It must be a local spatial query. See [Delivery Zones](/use-cases/delivery-zones). **[Geocoding](/guides/geocoding)** — address to point, cached permanently. **Why:** you need a coordinate for containment. You need it once per address, ever. **[Matrix Routing](/guides/matrix-routing)** — courier assignment. **Why:** "which of 40 available couriers is nearest to this restaurant" is a 40×1 matrix, not 40 routing calls. At order volume, this is the difference between viable and not. **[Routing](/guides/routing)** — courier ETA and navigation geometry. **Why:** once assigned, the courier needs a path. Use `transportMode=scooter` or `bicycle` where legally and physically appropriate — a car route for a scooter is wrong in both directions. `bicycle` and `scooter` transport modes carry beta status with limited functionality in HERE Routing v8. Validate them against your market before building courier ETAs on them. See [HERE's transport modes reference](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/topics/transport-modes.html). **Autocomplete** — address entry. **Why:** `/autocomplete` completes *addresses*. `/autosuggest` handles misspellings and suggests *places*. For a delivery address field you want the former. See [Geocoding and Search](/guides/geocoding). **Not for restaurant discovery.** See Alternatives. ## Implementation flow 1. **Materialize delivery zones** per restaurant, per band, at peak departure time. 2. **Checkout: autocomplete → geocode (cached) → `ST_Contains`.** Zero external calls on a cache hit. 3. **Return the band.** Delivery fee is a table lookup keyed on band. 4. **On order confirm**, build a small matrix of available couriers against the restaurant. 5. **Assign** by travel time plus your own business rules — courier rating, current load, shift end. 6. **Route the courier**, restaurant to customer, in the correct transport mode. 7. **Refresh ETA** from GPS position plus remaining route, not by re-routing on every ping. ## Data flow Serviceability is a **read from your own database**. Delivery fee is a **read from your own table**, keyed on the band that read returned. The only per-order external calls are one small matrix and one route. That is the target, and it is achievable. Dynamic delivery pricing lives naturally here: `deliveryConditions` per band, mapping order value to delivery price. That mapping is business data in your database, informed by the zone geometry. It is not something a routing API returns. ## Production considerations **Debounce autocomplete.** It fires on keystrokes. Undebounced, you bill once per character typed, per session, forever. 200–300ms. **Peak zones are the real zones.** Compute isolines at dinner rush. A midnight polygon promises delivery times you cannot meet at 7pm. **Courier ETA and delivery promise are different numbers.** The promise includes food preparation time, which no routing API knows. Model it separately or your ETAs will be optimistic in a way customers remember. **Reassignment is a new matrix.** A courier cancels. You are re-solving, not patching. **Do not re-route on GPS ping.** Recompute ETA from remaining route geometry and current position. Re-routing on every ping at courier fleet scale is the reverse-geocoding-every-ping mistake wearing a different hat. **Transport mode must match reality.** A scooter courier routed as a car takes forbidden turns and is given illegal highway segments. In dense markets this is a safety issue, not an accuracy issue. ## Scaling **Checkout scales for free.** PostGIS containment against an indexed polygon set does not care about your traffic. **Geocode cache hit rate approaches 1** as your customer base matures. New addresses are the only cost. **Matrix size is bounded by courier availability**, not by order volume. 40 couriers × 1 restaurant is a trivial matrix, computed thousands of times a day, and it is still vastly cheaper than 40 routing calls. **Zone count grows with restaurant count.** A 5,000-restaurant marketplace with three bands is 15,000 polygons. PostGIS handles that. The quarterly isoline cost is 15,000 calls — bounded, forecastable, and unrelated to order volume. ## Cost optimization 1. **Never call an API at checkout.** Materialized zones, cached geocodes. 2. **Debounce autocomplete.** The single largest unforced cost in consumer delivery apps. 3. **Matrix for assignment, never routing loops.** 4. **ETA refresh from geometry, not from re-routing.** 5. **Cache the restaurant's outbound geometry** to frequent delivery clusters. 6. **Deduplicate before batch geocoding** your historical order table. Order exports repeat addresses enormously. The cost structure you want: **bounded by locations and couriers, not by sessions and pings.** ## Common mistakes **Routing API at checkout for serviceability.** Works. Fatal at scale. **Radius delivery zones.** See [Delivery Zones](/use-cases/delivery-zones). **Undebounced autocomplete.** **Car routing for scooter couriers.** **Assuming beta transport modes are production-ready.** **Re-routing on every courier GPS ping.** **Assigning couriers by straight-line distance.** A courier 800m away across a river is not close. **Treating courier ETA as the delivery promise.** Preparation time is not routing. **Using HERE for restaurant discovery.** See below. **Re-geocoding a repeat customer's address.** ## Alternatives — honestly **For restaurant discovery — names, cuisines, hours, photos, ratings — Google's place data is categorically better than HERE's.** If your marketplace's value is helping users find a restaurant, that surface belongs on Google. Placematic sells HERE. We would rather you know this now than after a migration. The architecture that works: **HERE for zones, geocoding, courier routing, and assignment. Google for restaurant search, listings, and consumer autocomplete.** Two vendors, each doing what it is good at. Document that split as a decision before someone frames it as a failed migration. **Mapbox** is a strong choice for the customer-facing tracking map if visual polish is a differentiator. The routing core stays on HERE. **Straight-line distance** is genuinely adequate for courier assignment in a dense uniform grid over very short distances. Manhattan, sub-kilometre. Be honest about whether you are in that case; most markets are not. **Your own zones, hand-drawn** by operations, need no isoline API at all. PostGIS and a polygon editor. If ops already knows the boundaries and defends them politically, do not buy an API to argue with them. ## Related guides The materialization pattern this entire architecture depends on. Courier assignment without the routing loop. Autocomplete versus autosuggest, debouncing, and caching. Where the place-data gap decides your architecture. Also: [Catchment Area](/guides/catchment-area) · [Routing](/guides/routing) · [Store Locator](/use-cases/store-locator) ## HERE documentation * [Transport modes](https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/topics/transport-modes.html) * [Matrix Routing API v8](https://www.here.com/docs/category/matrix-routing-api-v8) * [Geocoding & Search v7](https://www.here.com/docs/category/geocoding-search-api-v7) ## Placematic * [Catchment Area](https://placematic.com/here-location-services/catchment-area/) * [HERE Location Services](https://placematic.com/here-location-services/) *** See the packaged solution for your industry: [Logistics & Delivery](https://placematic.com/solutions/logistics/) Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate costs, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Retail Expansion and Site Selection Source: https://docs.placematic.com/use-cases/site-selection Site selection architecture — drive-time trade areas, cannibalization modelling, the demographic join, and why the mapping API is the least interesting part of this system. # Retail Expansion and Site Selection ## The business problem Where should the next store go, and what will it do to the stores you already have? The second half is the half that gets skipped. A site with excellent demographics that sits inside an existing store's trade area does not add revenue. It relocates it, and now you pay two leases. ## Typical users Retail expansion teams. Restaurant franchise development. Real estate analytics platforms. Healthcare network planning. QSR and convenience chains. ## Recommended architecture ```mermaid theme={null} flowchart TB subgraph Geo["Base geography"] H3[H3 cells or
census units] DEMO[Demographics
ACS, income, households] COMP[Competitor locations] EXIST[Existing network] end subgraph Reach["Reachability — the API layer"] ISO[Isolines
trade areas by band] MX[Matrix Routing
candidates × demand cells] end subgraph Model["Analysis"] TA[Trade area capture] CANN[Cannibalization
overlap with existing] SCORE[Site score] end subgraph Out["Decision"] RANK[Ranked candidates] MAP[Map for the real estate committee] end H3 --> MX DEMO --> TA COMP --> SCORE EXIST --> ISO ISO --> CANN MX --> TA --> SCORE CANN --> SCORE --> RANK --> MAP ``` The routing API occupies one box. The other five are your data, your model, and your judgement. Teams that lead with the mapping vendor build a beautiful map and a bad decision. ## Which HERE APIs, and why **[Catchment Area](/guides/catchment-area)** — trade areas. **Why:** a 10-minute drive-time polygon is what a trade area actually is. A radius includes the lake, the airport, and the far side of the interstate with no exit. **[Matrix Routing](/guides/matrix-routing)** — demand capture. **Why:** "how long from each demand cell to each candidate site" is a matrix. It lets you model gravity-based capture rather than binary containment, which is more honest and produces better rankings. **[Batch Geocoding](/guides/batch-geocoding)** — competitor and candidate addresses to points. Deduplicated, once. **[Maps](/guides/maps)** — rendering, for the humans who sign the lease. **Not [POI search](/guides/points-of-interest) for competitor data.** Competitor location data sourced from a public place index will be stale, incomplete, and inconsistent in categorization. A store that closed six months ago still appears. Buy competitor data from a data vendor, or scrape it deliberately and own the freshness. Do not build an expansion strategy on a mapping API's place index. ## Implementation flow 1. **Choose a demand grid.** H3 at resolution 8 works well — roughly 0.7 km² cells, small enough to be meaningful, coarse enough to compute. 2. **Join demographics to cells.** US Census ACS, household counts, income, daytime population. 3. **Compute isolines** for every existing store, at your trade-area bands, in the transport mode your customers use. 4. **Compute isolines for each candidate site.** 5. **Overlap analysis.** Candidate trade area ∩ existing trade areas = cannibalization exposure. 6. **Matrix from demand cells to candidates** for gravity-weighted capture. 7. **Score.** Capture minus cannibalization, adjusted for competitor presence, cost, and whatever your business believes. 8. **Rank, map, and hand to the committee.** ## Data flow Everything is **materialized and versioned**. This is an analytical system. Nothing runs in a request path. Isolines are computed **once per site, per band**. Store the polygons. A 400-store network with three bands is 1,200 calls — quarterly, not daily. The matrix is computed **once per candidate set**, cached, and reused across every scenario the committee asks for. The committee will ask for twelve scenarios. If each scenario re-computes isolines, you have built a slow, expensive system that discourages the exploration it exists to enable. Materialize once. Explore for free. ## Production considerations **Cannibalization is the whole point.** A site score that ignores overlap with your existing network optimizes for gross revenue, not incremental. Every expansion team has a story about the store that killed its neighbour. **Binary containment overstates capture.** A customer 9 minutes from store A and 10 minutes from store B does not shop exclusively at A. Gravity models — capture decaying with drive time — are more honest, and the matrix gives you the inputs. **Transport mode matters more than it seems.** Urban customers walk or take transit. Suburban customers drive. A 10-minute drive-time polygon in Manhattan describes a customer base that does not exist. **Peak versus off-peak.** A trade area computed at 3am is larger than the one your customers actually experience at 6pm. **Demographics have a vintage.** ACS 5-year estimates are exactly that. Do not present them with more precision than they carry. **Version your analysis.** When the store underperforms in year two, someone will ask what the model predicted and on what data. ## Scaling **Isoline cost is bounded by site count.** Existing stores plus candidates, times bands. Quarterly. **Matrix cost is bounded by candidates × demand cells.** Restrict cells to those within the maximum band of any candidate — most of the country is irrelevant to any given site. **H3 resolution is the scaling dial.** Resolution 8 nationally is a very large number of cells. Resolution 6 for screening, 8 for finalists. **The join is the expensive part, and it is in PostGIS, not HERE.** Spatial joins over millions of cells are a database engineering problem. **Scenario exploration must be free.** Materialize everything; let the analyst iterate. ## Cost optimization 1. **Screen coarsely, analyze finely.** Resolution 6 to shortlist, resolution 8 for the final five. 2. **Restrict demand cells** to the union of candidate trade areas before any matrix call. 3. **Cache existing-store isolines.** They change when you open or close a store. 4. **Simplify polygons** before the overlap join. They were approximations. 5. **Batch-geocode competitor and candidate addresses.** 6. **Never compute an isoline inside an interactive tool.** Precompute. Cost is bounded by site count and candidate count — both business numbers you already know. This should be a four-figure annual line, not a variable one. ## Common mistakes **Radius trade areas.** Lakes, airports, limited-access highways. **Ignoring cannibalization.** Relocating revenue and calling it growth. **Binary containment instead of distance-decayed capture.** **Driving isolines for an urban walk-in customer base.** **Sourcing competitor locations from a public place index.** **Off-peak trade areas presented as customer reality.** **Computing isolines interactively.** Slow, expensive, discourages exploration. **H3 resolution 9 nationally.** Then wondering about the bill. **Presenting ACS estimates without their vintage or margin of error.** **No versioning.** Year two, no defence. ## Alternatives — honestly **Google Maps Platform** does not expose isoline polygons in an equivalent form. For trade-area geometry, HERE is the more direct fit. Google's Places data is better for competitor intelligence than HERE's — though as stated above, neither is good enough for an expansion decision. **Esri Business Analyst** is the incumbent in this space and it is genuinely good. If site selection is your firm's business, Esri's demographic data, drive-time capability, and analyst tooling are ahead of anything you will assemble from a routing API and PostGIS. It costs accordingly. Be honest about which you are: a retailer doing three expansions a year, or a platform doing this at scale for clients. The first should probably buy Esri or a consultancy. The second should build. **Commercial site-selection vendors** — Buxton, Placer.ai, SafeGraph — sell foot traffic and visitation data that no mapping API produces. Actual observed visits beat modelled capture. If the budget exists, this is a better input than any drive-time polygon. **Pure demographics with no reachability** is a real methodology and it is common. It is also how retailers end up with stores separated by a river. Drive time earns its place. **Placematic UpInsight** is spatial analytics for business teams making location decisions — trade-area measurement, site scoring, expansion without cannibalization. It is built on the architecture above. Evaluate it against building this yourself, and against Esri. The honest comparison is: Esri if site selection is your core discipline and budget is not the constraint; UpInsight if you want the workflow without owning the pipeline; build it if you have a spatial data team and specific requirements neither fits. ## Related guides Trade areas as drive-time polygons, and the materialization pattern. Demand cells × candidates, for gravity-weighted capture. The analytical layer this feeds. Once the stores exist, someone has to cover them. Also: [Batch Geocoding](/guides/batch-geocoding) · [Points of Interest](/guides/points-of-interest) · [Delivery Zones](/use-cases/delivery-zones) ## HERE documentation * [Routing API v8](https://www.here.com/docs/category/routing-api-v8) — isoline routing * [Matrix Routing API v8](https://www.here.com/docs/category/matrix-routing-api-v8) * [Batch API v7](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/batch-api-quick-start.html) ## Placematic * [UpInsight — spatial analytics](https://placematic.com/spatial-data-analytics/) * [Catchment Area](https://placematic.com/here-location-services/catchment-area/) *** See the packaged solution for your industry: [Network planning](https://placematic.com/solutions/network-planning/) Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate costs, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Building a Modern Store Locator Source: https://docs.placematic.com/use-cases/store-locator Store locator architecture on HERE — why /browse and /discover rank differently, the query that should never hit an API, and the build-versus-buy line most teams cross too late. # Building a Modern Store Locator ## The business problem "Where is your nearest location, and how do I get there?" It is the most-visited page on most multi-location brand sites, and it is almost always built twice. Once badly, with a radius query over a database. Then again, after someone notices the "nearest" store is across a river. ## Typical users Multi-location retail brands. Restaurant chains. Franchise networks. Dealership groups. Banks and credit unions. Healthcare networks. ## Recommended architecture ```mermaid theme={null} flowchart LR subgraph Own["Your data — never HERE"] STORES[(Store table
+ PostGIS geometry)] HOURS[Hours, services,
inventory flags] end subgraph Entry["User enters location"] AC[Autocomplete] GC[Geocode + cache] GEO[Browser geolocation] end subgraph Rank["Candidate → ranked"] NEAR[ST_DWithin
k nearest by distance] MX[Matrix Routing
drive time to k] SORT[Rank by drive time] end subgraph Out["Result"] MAP[Map tiles + markers] DIR[Directions link] end STORES --> NEAR AC --> GC --> NEAR GEO --> NEAR NEAR --> MX --> SORT HOURS --> SORT SORT --> MAP --> DIR ``` The two-stage ranking is the whole design. Straight-line distance narrows the field cheaply. Drive time orders it correctly. ## Which HERE APIs, and why **Autocomplete** — address entry. **Why:** `/autocomplete` completes *addresses*. `/autosuggest` handles misspellings and suggests *places*. A store locator's input box wants the former. See [Geocoding and Search](/guides/geocoding). **[Geocoding](/guides/geocoding)** — the user's input to a point. Cache it. The same three ZIP codes will be typed a million times. **[Matrix Routing](/guides/matrix-routing)** — ranking the shortlist. **Why:** 1 origin × 8 candidate stores is one matrix call. Eight routing calls is eight. At store-locator traffic, that ratio is your entire cost structure. **[Maps](/guides/maps)** — tiles. **Why:** you need a map. Tiles work with Leaflet, OpenLayers, and MapLibre GL. If you already render with one of those, integration is a tile URL swap. **Not [POI search](/guides/points-of-interest).** Your stores live in **your** database. Querying HERE's public place index to find your own locations is fragile, expensive, and will silently miss the store that opened last week. Map data ships on a release cadence. Your store table does not. ## Implementation flow 1. **Geocode your store network once.** [Batch](/guides/batch-geocoding) it. Store the geometry in PostGIS with a spatial index. 2. **User enters a location** — autocomplete, then geocode, then cache. Or take browser geolocation directly. 3. **Shortlist with `ST_DWithin`.** Take the k nearest by straight-line distance. k = 8 to 12. This is free. 4. **Rank the shortlist by drive time** with a single matrix call, origin × k. 5. **Filter by business rules** — open now, has pharmacy, in stock — from your own data. 6. **Render** the map and the ordered list. 7. **Directions** are a routing call, and only when the user asks. Step 3 exists to make step 4 cheap. Matrix over your *entire* 2,000-store network is wasteful; matrix over 10 candidates is trivial. Straight-line distance is a bad ranker and an excellent filter. ## Data flow Store data flows **out of your database, never into a mapping API**. The user's coordinate is the only external geocoding call, and it hits cache constantly — the input space is small and repetitive. The matrix call is bounded at origin × k, regardless of network size. ## Production considerations **Straight-line nearest is not nearest.** A store 800m away across a river, a rail line, or a limited-access highway is not close. This is the defect that gets store locators rebuilt. **Cache the ranking, not just the geocode.** For a given user coordinate rounded to \~3 decimal places, the drive-time ranking of nearby stores is stable for weeks. Cache the ordered result. **Debounce autocomplete.** It fires on keystrokes. Undebounced, you bill once per character. 200–300ms. **Handle "no stores nearby" deliberately.** Expanding the radius silently until something is found produces a locator that cheerfully sends people 200 miles. **Hours, services, and inventory are your data.** They change hourly. No mapping API knows them. Do not attempt to source them externally. **Directions on demand only.** Most visitors never click. Routing on page render bills for a feature nobody used. **Restrict browser-exposed keys by domain.** Any key in frontend JavaScript is public. See [Authentication](/getting-started/authentication). ## Scaling **PostGIS `ST_DWithin` against an indexed geometry column is sub-millisecond** and indifferent to your traffic. **Matrix cost is bounded by k, not by store count.** A 5,000-store network and a 50-store network cost the same per lookup. **The ranking cache is the scaling lever.** Round the user coordinate. Dense urban areas produce enormous hit rates. **Geocode cache hit rate approaches 1** on a store locator. Users type city names and ZIP codes, not distinct street addresses. ## Cost optimization 1. **Shortlist locally before any API call.** `ST_DWithin` first, always. 2. **Cache the ranked result**, keyed on rounded user coordinate. 3. **Debounce autocomplete.** 4. **Directions on click, never on render.** 5. **CDN in front of tiles.** They are static per zoom/x/y/style. 6. **Never query a public place index for your own stores.** 7. **Batch-geocode the store network once.** It changes when you open a store. Target cost structure: **bounded per lookup, independent of network size, dominated by cache misses.** ## Common mistakes **Ranking by straight-line distance.** Rivers. **Matrix over the entire store network** instead of a shortlist. **Routing calls to rank** instead of one matrix call. **Querying HERE's POI index for your own stores.** **Undebounced autocomplete.** **Computing directions on page load.** **Unrestricted API key in frontend JavaScript.** **Silently expanding the search radius** until a result appears. **No CDN on tiles.** **Using `/discover` for the store list.** It ranks by relevance. `/browse` ranks by distance. Neither is right here — your stores are your data. ## Alternatives — honestly **Google Maps Platform** is the better choice if your locator must also surface *other people's* businesses — reviews, photos, hours from a public index. It is not better for finding your own stores, which are in your database either way. The map's appearance is the real difference most brands are choosing between. **Mapbox** is a strong choice when the map's visual identity is part of the brand. Styling depth exceeds HERE's. The ranking core is a matrix call either way, and you would still need a routing provider. **No routing provider at all** is defensible for a small, sparse network — twelve stores across a state. Straight-line distance is correct when the nearest store is 40 miles away and the second nearest is 90. Buy the API when store density makes drive time diverge from distance. **Placematic Pos-Eye** is an embeddable store locator over your own location data, with HERE-powered maps and real-time autocomplete. It exists because the architecture above is well understood, and most brands do not want to own it. The trade-off is the usual one: build it and control it, or embed it and ship this quarter. Evaluate it as a buy decision, not a technical one — everything on this page is what you would be building. ## Related guides One origin, k candidates, one call. Autocomplete versus autosuggest, debouncing, and caching. Tiles, CDN caching, and browser key restriction. Why your stores are not a POI query — and where Google wins. Also: [Delivery Zones](/use-cases/delivery-zones) · [Site Selection](/use-cases/site-selection) · [Batch Geocoding](/guides/batch-geocoding) ## HERE documentation * [Geocoding & Search v7](https://www.here.com/docs/category/geocoding-search-api-v7) * [Matrix Routing API v8](https://www.here.com/docs/category/matrix-routing-api-v8) * [HERE Map Rendering](https://www.here.com/docs/category/here-map-rendering) ## Placematic * [Pos-Eye — embeddable store locator](https://placematic.com/store-locator/) * [HERE Location Services](https://placematic.com/here-location-services/) *** See the packaged solution for your industry: [SaaS & Product Teams](https://placematic.com/solutions/saas-teams/) Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate costs, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Building Sales and Operational Territories Source: https://docs.placematic.com/use-cases/territory-management Territory design on HERE — drive-time versus administrative boundaries, the balancing problem nobody warns you about, and why territory systems are 80% database and 20% API. # Building Sales and Operational Territories ## The business problem Divide a market among 40 sales reps, or 200 service technicians, such that each territory is workable, roughly balanced, contiguous, and politically defensible. The politics are not a joke. A territory system that produces a mathematically optimal assignment nobody accepts has failed completely. ## Typical users Sales operations. Field service management. Franchise development. Distribution and merchandising. Insurance and financial services field teams. ## Recommended architecture ```mermaid theme={null} flowchart TB subgraph Base["Base geography"] UNITS[Assignment units
ZIP, county, H3 cell] ATTR[Attributes
accounts, revenue, households] end subgraph Reach["Reachability"] BASES[Rep home bases] MX[Matrix Routing
bases × units] ISO[Isolines
coverage envelopes] end subgraph Design["Territory design"] ASSIGN[Assignment
balance + contiguity] HUMAN[Manual override
by ops] end subgraph Store["System of record"] PG[(PostGIS
territory polygons)] VER[Versioned, effective-dated] end subgraph Use["Consumption"] LOOKUP[Account → territory] REPORT[Territory reporting] end UNITS --> MX ATTR --> ASSIGN BASES --> MX --> ASSIGN BASES --> ISO --> ASSIGN ASSIGN --> HUMAN --> PG --> VER PG --> LOOKUP PG --> REPORT ``` Notice what dominates this diagram: your database. Territory management is a data modelling problem with a routing API attached, not the reverse. Teams that lead with the API build the wrong system. ## Which HERE APIs, and why **[Matrix Routing](/guides/matrix-routing)** — the reachability substrate. **Why:** territory design needs drive time from each rep base to each assignment unit. That is exactly a matrix. 40 bases × 800 ZIP centroids is one problem, not 32,000 routing calls. **[Catchment Area](/guides/catchment-area)** — coverage envelopes. **Why:** "everything within a 45-minute drive of this rep" is a reachability polygon, computed once, stored. Useful for visualizing coverage and gaps, not for assignment. **[Batch Geocoding](/guides/batch-geocoding)** — accounts to points. **Why:** you have 200,000 accounts with addresses. Nothing is waiting. Batch, deduplicate, cache forever. **[Maps](/guides/maps)** — rendering territory polygons for the humans who must approve them. **Not [Routing](/guides/routing).** A territory system does not route. It assigns. **Isolines define coverage. Matrix defines assignment.** Two reps' 45-minute polygons overlap heavily. Assigning accounts by polygon membership produces arbitrary results in exactly the overlap regions where your densest accounts live. Assign by travel time. ## Implementation flow 1. **Choose the assignment unit.** ZIP code, county, or an H3 cell at resolution 7–8. This decision constrains everything downstream and is hard to reverse. 2. **Attach attributes** to each unit — account count, revenue, households, workload proxy. 3. **Batch-geocode accounts** to points; aggregate them to units. 4. **Build the matrix**: rep bases × unit centroids. 5. **Assign** with balance and contiguity constraints. This is an optimization you own — see Alternatives. 6. **Give ops a manual override.** Non-negotiable. 7. **Store territories as versioned, effective-dated polygons** in PostGIS. 8. **Account lookup is `ST_Contains`.** It never calls an API. ## Data flow The **unit** is the atom. Accounts roll up to units. Units roll up to territories. Nothing is assigned at the account level, because account-level assignment produces non-contiguous territories that look like confetti. Territory membership is answered **locally, forever**. A CRM asking "whose account is this" must never make a network call to a mapping vendor. ## Production considerations **Effective-date everything.** Territories change at quarter boundaries. Commission disputes reach back twelve months. A territory table without effective dates will be rebuilt within a year, under pressure, by someone angry. **Contiguity is a hard requirement, not a nicety.** A rep whose territory is three disconnected islands drives more than they sell. Most naive assignment algorithms produce exactly that. **Balance is multi-objective and contested.** Balance by account count, revenue, drive time, or workload? Different answers, all defensible. Pick one, state it explicitly, and expect to be argued with. **Overlap is sometimes correct.** Overlay territories — a specialist covering three generalist territories — are a legitimate model. Do not force a partition where the business runs a hierarchy. **The manual override is the feature.** Ops knows that the rep in Cleveland has a relationship with the account in Akron. Model the override, log who made it and why, and preserve it across re-runs. Re-running the optimizer and silently discarding last quarter's manual overrides is how a territory system loses its users permanently. Treat overrides as constraints on the next solve, not as edits to be overwritten. **Centroids lie.** A ZIP code's geometric centroid may sit in a lake, or on the wrong side of a highway. Use a population-weighted centroid, or the mean of geocoded accounts within the unit. ## Scaling **The matrix is the only meaningful API cost, and it is bounded.** 40 reps × 800 units, recomputed quarterly. That is small. **Cache it.** Rep home bases and ZIP centroids do not move. **Unit count drives everything.** ZIP-level for a national US footprint is roughly 40,000 units. H3 resolution 8 across a state is far more. Choose the coarsest unit that produces acceptable territories. **Account lookup scales for free.** Indexed PostGIS containment. **Re-optimization is quarterly, not continuous.** There is no scaling problem here that a cron job does not solve. ## Cost optimization 1. **Aggregate accounts to units before any spatial computation.** Never matrix 200,000 accounts. 2. **Cache the rep-base × unit matrix.** It is stable for a quarter. 3. **Batch-geocode accounts once**, deduplicated. Address exports repeat heavily. 4. **Coarse isolines** for coverage visualization. The map renders identically. 5. **Never call an API for territory lookup.** `ST_Contains`. 6. **Choose the coarsest workable unit.** Cost is bounded by rep count times unit count, recomputed quarterly. This is a forecastable line item measured in hundreds of calls, not millions. ## Common mistakes **Assigning accounts individually.** Non-contiguous territories. **Assigning by isoline membership.** Arbitrary in overlaps. **Straight-line distance for reachability.** Rivers, mountains, limited-access highways. **Geometric centroids.** Lakes, wrong side of the freeway. **No effective dating.** Commission disputes. **Silently overwriting manual overrides.** The system loses its users. **Optimizing for a single objective** without stating which. **Forcing a partition** where the business runs overlay territories. **Calling a mapping API for territory lookup from a CRM.** **Choosing H3 resolution 9 nationally** and then wondering why the matrix is enormous. ## Alternatives — honestly **Administrative boundaries alone** — ZIP, county, state — require no routing API and are how most territory systems actually run. They are politically legible, stable, and align with reporting. Drive time matters when territories are large, rural, or bisected by geography. In dense metros, ZIP-based territories are often indistinguishable from optimal, and far easier to defend in a room. Be honest about whether you have a routing problem or a spreadsheet problem. **Google Maps Platform** offers a Distance Matrix that can substitute for HERE's in this workflow. HERE's advantage here is thin unless you also need truck reachability or you are already licensing it for routing. **Commercial territory tools** — Salesforce Maps, eSpatial, Xactly — solve the balancing, contiguity, and override workflow you would otherwise build. If territory design is an annual ops exercise rather than a product feature, buy it. The optimization is not the hard part; the workflow is. **Your own solver** with OR-Tools handles balance and contiguity well. Feed it a cached matrix. This is a reasonable build for a team that owns the domain. **Placematic UpGrid** is browser-based territory management over your own location data — define service zones, assign locations, query territory data via API, without GIS expertise on staff. Evaluate it as a buy decision against the workflow above. What you are choosing is not an algorithm; it is who maintains the override log at quarter close. ## Related guides The reachability substrate. Bases × units, once per quarter. Coverage envelopes — for visualization, not assignment. 200,000 accounts, deduplicated, once. When dynamic assignment beats fixed territories. Also: [Site Selection](/use-cases/site-selection) · [Location Intelligence](/use-cases/location-intelligence) · [Delivery Zones](/use-cases/delivery-zones) ## HERE documentation * [Matrix Routing API v8](https://www.here.com/docs/category/matrix-routing-api-v8) * [Routing API v8](https://www.here.com/docs/category/routing-api-v8) — isoline routing * [Batch API v7](https://www.here.com/docs/bundle/batch-api-v7-developer-guide/page/topics/batch-api-quick-start.html) ## Placematic * [UpGrid — territory management](https://placematic.com/territory-management/) * [UpInsight — spatial analytics](https://placematic.com/spatial-data-analytics/) *** See the packaged solution for your industry: [Retail & Franchise](https://placematic.com/solutions/retail/) Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate costs, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/). # Real-Time Vehicle Tracking Architecture Source: https://docs.placematic.com/use-cases/vehicle-tracking Ingesting GPS at scale — why nothing expensive should touch a packet on arrival, stop detection as the unit of work, and the ratio that tells you whether your bill is broken. # Real-Time Vehicle Tracking Architecture The defining constraint of vehicle tracking is that packets arrive whether or not anyone needs them. A vehicle emits a position every ten seconds. Over a nine-hour shift that is 3,240 packets. Across 200 vehicles, 648,000 per day. Across a thousand vehicles on a five-second interval, over six million. **Any API call attached to a packet multiplies by that number.** That single sentence determines whether your platform has a viable cost structure. ## The problem The naive architecture is obvious and wrong: packet arrives → reverse geocode → store address → update map It works at ten vehicles. It works in staging. It produces a bill at 200 vehicles that nobody forecast, because nobody counted packets. Worse, it produces no value. The address is needed when a human looks at a screen, when a stop is detected, or when a report is generated. It is not needed at 3:47:20am for a truck on I-80. Reverse-geocoding GPS packets on ingest is the most expensive default in fleet software. It is also invisible in code review, because the code is correct and the volume is elsewhere. ## Who this is for Telematics engineers. Fleet platform teams. ELD vendors. Anyone whose system has a `POST /positions` endpoint. ## Recommended architecture ```mermaid theme={null} flowchart TB subgraph Hot["Ingest — cheap, always, no APIs"] PKT[GPS packet] VAL[Validate + dedupe] RAW[(Raw position store)] LAST[Last-known position
in-memory / Redis] end subgraph Events["Event detection — cheap, local"] STOP[Stop detection
stationary > N min] GEO[Geofence transitions
ST_Contains] DEV[Route deviation] end subgraph Lazy["On demand — a human is looking"] RG[Reverse geocode
selected vehicle only] MAP[Map tiles] end subgraph Batch["Overnight — nothing waiting"] SEG[Trip segmentation] RM[Route Matching] LABEL[Batch reverse geocode
stop labels] end PKT --> VAL --> RAW VAL --> LAST LAST --> STOP LAST --> GEO LAST --> DEV STOP --> LABEL LAST --> RG RAW --> SEG --> RM ``` Four tiers. Only two of them ever call an external API, and neither is in the packet path. ## Relevant HERE APIs, and why **[Reverse Geocoding](/guides/reverse-geocoding)** — on stop detection and on view. **Why:** `POST /multi-revgeocode` resolves a *list* of coordinates in one request. If you are looping single-coordinate calls over a batch of detected stops, you have made the same mistake one layer up. **[Route Matching](/guides/route-matching)** — overnight, on closed trips. **Why:** turning a noisy trace into the road segments actually travelled. Required for IFTA, speed compliance, and any question a regulator might ask. See [ELD Platform](/use-cases/eld-platform). **[Maps](/guides/maps)** — tiles for the dispatcher's screen. Put a CDN in front of them. **[Routing](/guides/routing)** — on deviation, not on ping. See [ETA Calculation](/use-cases/eta-calculation). **Not [Positioning](/guides/positioning).** Outdoor road vehicles have GPS. It is more accurate, free, and already in the device. Hybrid positioning is for warehouses and urban canyons, and it improves exactly where GPS fails and degrades exactly where GPS works. ## Implementation flow 1. **Validate and store the packet.** That is all. No API calls. 2. **Maintain last-known position** in memory. This is what a live map reads. 3. **Detect stops locally.** Stationary beyond a threshold. This is arithmetic. 4. **Detect geofence transitions locally.** `ST_Contains` against your own polygons. See [Geofencing](/use-cases/geofencing). 5. **Reverse-geocode detected stops**, in a batch, with `multi-revgeocode`. 6. **Reverse-geocode on view**, for the one vehicle a dispatcher clicked. 7. **Segment, downsample, and match trips** overnight. Instrument the ratio of reverse-geocoding calls to detected stops. Anything meaningfully above 1 means you are geocoding packets. This single metric tells you more about your bill than any invoice line item. ## Cost considerations **Round coordinates before cache lookup.** GPS jitter defeats an exact-match cache. Round to roughly five decimal places — about one metre. A vehicle idling at a depot generates hundreds of packets within a metre of each other. One lookup. **Cache depot and customer locations permanently.** They are known, fixed, and finite. Resolve them once at onboarding. **Pass drive direction where telematics provides it.** On a divided highway, a coordinate 15 metres from the centreline resolves to either carriageway. Heading disambiguates. This is free accuracy and it reduces "the truck is on the wrong side of the interstate" support tickets. **Batch the historical backfill.** Enriching six months of trip history is a job, not a stream of real-time calls. **Segment trips before matching.** Dropping idle periods removes a substantial fraction of submitted points in real feeds. Do it in your pipeline. **Do not re-route on every packet to refresh an ETA.** Recompute the remaining duration from route geometry and current position. That is arithmetic on data you already hold. See [ETA Calculation](/use-cases/eta-calculation). **Evaluate asset-based pricing.** A countable vehicle population with unpredictable call volume is exactly the shape asset-based pricing exists for. See [HERE Pricing Explained](/getting-started/here-pricing-explained). ## Common mistakes **Reverse-geocoding on packet ingest.** The dominant cost error. **Looping `/revgeocode` over a list.** `multi-revgeocode` exists. **No coordinate rounding before cache lookup.** Sub-metre jitter, zero hits. **Re-routing on every position update** to refresh an ETA. **Ignoring heading.** Wrong carriageway. **Matching packets instead of trips.** **Submitting the parked portion of a trace.** **Using reverse geocoding to determine which road the vehicle drove.** That is [Route Matching](/guides/route-matching), and only Route Matching survives an audit. **Using reverse geocoding for geofence containment.** That is a spatial query. See [Geofencing](/use-cases/geofencing). **Storing the nearest address as the vehicle's true location.** It is a nearest match — in rural areas, potentially several hundred metres away on a different property. Store the coordinate too. **Using hybrid positioning for outdoor road vehicles.** They have GPS. ## Production checklist * [ ] Zero external API calls in the packet ingest path * [ ] Last-known position served from memory, not from a geocoded record * [ ] Stop detection implemented locally, on a stationary threshold * [ ] Geofence transitions computed with `ST_Contains`, not an API call * [ ] Reverse-geocode-call-to-detected-stop ratio measured and near 1 * [ ] `multi-revgeocode` used for lists; no loops of single calls * [ ] Coordinates rounded to \~5 decimal places before cache lookup * [ ] Depot and customer coordinates resolved once, permanently * [ ] Drive direction passed when telematics supplies it * [ ] Trip segmentation drops idle periods before matching * [ ] Trip matching runs as an overnight batch job * [ ] Raw coordinate persisted alongside any resolved address * [ ] ETA refresh computed from geometry, not by re-routing ## Alternatives and trade-offs **Google Maps Platform** reverse geocoding is comparable. The cost problem described on this page is architectural and survives any migration. Fix the architecture first; the platform decision is easier afterwards and may become unnecessary. **Do the reverse geocoding yourself.** For a bounded operating area, a Nominatim instance over OSM extracts, or a PostGIS nearest-neighbour query against an address table, handles stop labelling adequately and costs nothing per call. This is a real option for platforms operating in a single metro. It does not replace [Route Matching](/guides/route-matching), which is the hard part. **Store coordinates and never resolve them.** Many dispatcher UIs show a marker on a map. A marker does not need an address. Ask whether the address is genuinely read by a human, or whether it is stored because it seemed like the right thing to store. **A telematics platform** — Samsara, Motive, Geotab — solves ingest, stop detection, and trip segmentation, and sells you the result. If tracking is infrastructure rather than your product, buy it. If you are building an ELD or TMS product, this is your product and you should own it. **Increase your sampling interval.** Before optimizing the pipeline, ask whether ten seconds is required. For long-haul highway operation, thirty seconds loses little and cuts everything downstream by two thirds. For urban delivery with frequent stops, it does not. This is a device configuration decision that dominates every architectural one below it. ## Related guides `multi-revgeocode`, coordinate rounding, and drive-direction snapping. Turning traces into road segments — the batch job that survives an audit. Why containment never calls an API. Refreshing arrival times without re-routing on every packet. Also: [ELD Platform](/use-cases/eld-platform) · [Fleet Routing](/use-cases/fleet-routing) · [Positioning](/guides/positioning) ## HERE documentation * [Geocoding & Search v7](https://www.here.com/docs/category/geocoding-search-api-v7) * [Routing API v8](https://www.here.com/docs/category/routing-api-v8) *** See the packaged solution for your industry: [Fleet & ELD](https://placematic.com/solutions/fleet-eld/) Need help designing or implementing a production HERE solution? Placematic helps engineering teams select the right HERE APIs, estimate usage, migrate from Google Maps and build production-ready geospatial systems. [Talk to us](https://placematic.com/contact/).