Using Map Attributes API (formerly Fleet Telematics API) to extract map data with JavaScript
Applies To
----------
HERE Maps API for JavaScript 3.1.x
HERE Map Attributes API v8 (formerly Fleet Telematics API)
Applications built on the deprecated H.service.extension namespace
Speed limit, conditional speed limit, variable speed limit and truck speed limit layers
Any tiled map attribute layer retrieved by bounding box or by tile
Symptoms / Triggers
-------------------
Customers may report one or more of the following:
Fleet Telematics helper classes are no longer available or no longer recommended in the HERE Maps API for JavaScript 3.1.x library.
Code based on the H.service.extension namespace stops being maintained or no longer matches current documentation.
Speed limits or other road attributes need to be displayed for the current map view, but there is no built-in JavaScript service for it.
It is unclear how to request map attributes for a bounding box versus for a set of tiles.
Requests return errors or incomplete data when too many layer and tile combinations are sent in a single call.
The same tiles are requested repeatedly as the user pans the map, causing unnecessary transactions.
Summary
-------
The Fleet Telematics functionality in the HERE Maps API for JavaScript 3.1.x library is deprecated. Map attribute data should now be retrieved by calling the HERE Map Attributes API v8 REST endpoint directly from JavaScript using fetch(), and parsing the JSON response in the application.
No additional HERE JavaScript library is required. The map object is only used to determine which area to request.
Answer
------
### Endpoint<br />https://smap.hereapi.com/v8/maps/attributes<br />
Key request parameters:
| Parameter | Purpose |
| --- | --- |
| layers | Comma-separated layer IDs, for example SPEED_LIMITS_FC1 |
| in | Spatial filter: bbox:west,south,east,north or tile:tileId1,tileId2,... |
| apikey | Your HERE platform API key |
| meta=1 | Returns layer metadata alongside the rows, needed to know which layer each result belongs to |
Layers are organised by functional class (FC 1 to 5), so a logical layer such as SPEED_LIMITS is requested as five separate layer IDs: SPEED_LIMITS_FC1 through SPEED_LIMITS_FC5.
Results are keyed by LINK_ID, which allows attributes from different layers to be merged into a single record per road segment.
### Option 1 - Search in a bounding box
Use this when you simply need everything inside the current map view. It is the easiest option to implement.<br />const retrieveSpeedLimitsInBbox = () => { const fcs = [1, 2, 3, 4, 5]; const layers = ["SPEED_LIMITS", "SPEED_LIMITS_VAR", "SPEED_LIMITS_COND", "TRUCK_SPEED_LIMITS"]; let allResp = []; let layerIds = []; layers.forEach((layer) => { fcs.forEach((fc) => { layerIds.push([layer, "_FC", fc].join("")); }); }); const bbox = map.getViewModel().getLookAtData().bounds.getBoundingBox(); const bboxPrm = encodeURIComponent( `bbox:${bbox.getLeft()},${bbox.getBottom()},${bbox.getRight()},${bbox.getTop()}` ); let cntReqs = layerIds.length; layerIds.forEach((layerId) => { const url = `https://smap.hereapi.com/v8/maps/attributes?layers=${layerId}∈=${bboxPrm}&apikey=${apikey}&meta=1`; fetch(url) .then((r) => { if (!r.ok) { throw new Error("Response was not ok"); } return r.json(); }) .then((d) => { parseData(d, --cntReqs === 0); }) .catch((error) => console.error("Problem with the fetch operation:", error)); }); function parseData(r, isLast) { allResp = [...allResp, r]; if (!isLast) { return; } const parsed = allResp.reduce((accum, currV) => { const layerId = currV.meta[0].layerId; currV.geometries.forEach((geom) => { const row = geom.attributes; accum[row.LINK_ID] = accum[row.LINK_ID] || {}; if (layerId.startsWith("SPEED_LIMITS_FC")) { accum[row.LINK_ID].FROM_REF_SPEED_LIMIT = row.FROM_REF_SPEED_LIMIT; accum[row.LINK_ID].TO_REF_SPEED_LIMIT = row.TO_REF_SPEED_LIMIT; } else if (layerId.startsWith("SPEED_LIMITS_VAR_FC")) { accum[row.LINK_ID].DIRECTION = row.DIRECTION; accum[row.LINK_ID].DATE_TIMES = row.DATE_TIMES; } else if (layerId.startsWith("SPEED_LIMITS_COND_FC")) { accum[row.LINK_ID].DATE_TIMES = row.DATE_TIMES; accum[row.LINK_ID].SPEED_LIMIT = row.SPEED_LIMIT; } else if (layerId.startsWith("TRUCK_SPEED_LIMITS_FC")) { accum[row.LINK_ID].TRUCK_FROM_REF_SPEED_LIMIT = row.FROM_REF_SPEED_LIMIT; accum[row.LINK_ID].TRUCK_TO_REF_SPEED_LIMIT = row.TO_REF_SPEED_LIMIT; accum[row.LINK_ID].TRAILER = row.TRAILER; } }); return accum; }, {}); console.log("parsed:", parsed); }}`;<br />
Notes on this pattern:
In the bounding box response, rows are found under geometries[].attributes, and the layer name is read from meta[0].layerId.
The sample issues one request per layer ID, so four layers across five functional classes results in 20 requests. Request only the layers and functional classes your use case needs.
### Option 2 - Search by tile
Use this when the map is panned frequently. Tiles are stable and cacheable, so already-fetched tiles can be skipped, which reduces transactions and improves responsiveness.
Tile level is derived from the functional class as level = FC + 8, so FC1 uses level 9 and FC5 uses level 13.<br />var tileIds = {};const retrieveSpeedLimitsInTile = () => { const lookAt = map.getViewModel().getLookAtData(); const bbox = lookAt.bounds.getBoundingBox(); // Only request map attributes at the intended zoom level if (Math.floor(lookAt.zoom) != 15) { return; } const pointTopLeft = { lat: bbox.getTop(), lng: bbox.getLeft() }; const pointBottomRight = { lat: bbox.getBottom(), lng: bbox.getRight() }; const fcs = [1, 2, 3, 4, 5]; const layers = ["SPEED_LIMITS", "SPEED_LIMITS_VAR", "SPEED_LIMITS_COND", "TRUCK_SPEED_LIMITS"]; let allResp = []; let arrGridTidsAllFcs = []; fcs.forEach((fc) => { const level = fc + 8; const tileXYTopLeft = getTileXY(pointTopLeft, level); const tileXYBottomRight = getTileXY(pointBottomRight, level); const arrGridTileIds = calcGridTileIds(tileXYTopLeft, tileXYBottomRight, level); arrGridTidsAllFcs = [...arrGridTidsAllFcs, ...arrGridTileIds]; arrGridTileIds.forEach((tileId) => { tileIds[tileId] = { fc: fc }; tileIds[tileId]["layers"] = layers.map((layer) => [layer, "_FC", fc].join("")); }); }); // Keep the number of layer and tile combinations per request within a safe limit const arrGridTileIdsBy64 = splitArrayEqually(arrGridTidsAllFcs, Math.floor(64 / layers.length)); const baseUrl = `https://smap.hereapi.com/v8/maps/attributes?&apikey=${apikey}&meta=1`; const inTile = "in=" + encodeURIComponent("tile:"); let cntReqs = arrGridTileIdsBy64.length; arrGridTileIdsBy64.forEach((item64) => { let urlTiles = []; let urllayerIds = []; item64.forEach((tilId) => { const tiles = Array.from({ length: tileIds[tilId].layers.length }, () => tilId); urlTiles = [...urlTiles, ...tiles]; urllayerIds = [...urllayerIds, ...tileIds[tilId].layers]; }); const url = [ `${baseUrl}`, `layers=${urllayerIds.join(",")}`, `${inTile}${urlTiles.join(",")}` ].join("&"); fetch(url) .then((r) => { if (!r.ok) { throw new Error("Response was not ok"); } return r.json(); }) .then((d) => { parseData(d, --cntReqs === 0); }) .catch((error) => console.error("Problem with the fetch operation:", error)); }); function parseData(r, isLast) { allResp = [...allResp, ...r.Tiles]; if (!isLast) { return; } const parsed = allResp.reduce((accum, currV) => { const layerId = currV.Meta.layerName; currV.Rows.forEach((row) => { accum[row.LINK_ID] = accum[row.LINK_ID] || {}; if (layerId.startsWith("SPEED_LIMITS_FC")) { accum[row.LINK_ID].FROM_REF_SPEED_LIMIT = row.FROM_REF_SPEED_LIMIT; accum[row.LINK_ID].TO_REF_SPEED_LIMIT = row.TO_REF_SPEED_LIMIT; } else if (layerId.startsWith("SPEED_LIMITS_VAR_FC")) { accum[row.LINK_ID].DIRECTION = row.DIRECTION; accum[row.LINK_ID].DATE_TIMES = row.DATE_TIMES; } else if (layerId.startsWith("SPEED_LIMITS_COND_FC")) { accum[row.LINK_ID].DATE_TIMES = row.DATE_TIMES; accum[row.LINK_ID].SPEED_LIMIT = row.SPEED_LIMIT; } else if (layerId.startsWith("TRUCK_SPEED_LIMITS_FC")) { accum[row.LINK_ID].TRUCK_FROM_REF_SPEED_LIMIT = row.FROM_REF_SPEED_LIMIT; accum[row.LINK_ID].TRUCK_TO_REF_SPEED_LIMIT = row.TO_REF_SPEED_LIMIT; accum[row.LINK_ID].TRAILER = row.TRAILER; } }); return accum; }, {}); console.log("parsed:", parsed); } // Build the grid of tile IDs between the top left and bottom right tile function calcGridTileIds(tileXYTopLeft, tileXYBottomRight, level) { const cntXs = tileXYBottomRight.tileX - tileXYTopLeft.tileX + 1; const cntYs = tileXYTopLeft.tileY - tileXYBottomRight.tileY + 1; const arrXs = Array.from({ length: cntXs }, (_, index) => index + tileXYTopLeft.tileX); const arrYs = Array.from({ length: cntYs }, (_, index) => index + tileXYBottomRight.tileY); const arrGridTileIds = []; arrXs.forEach(function (itemX) { arrYs.forEach(function (itemY) { const tileId = getTileId({ tileX: itemX, tileY: itemY }, level); if (!tileIds[tileId]) { // Skip tiles that were already fetched arrGridTileIds.push(tileId); } }); }); return arrGridTileIds; } function getTileXY(point, level) { const degSize = 180 / Math.pow(2, level); const tileY = Math.floor((point.lat + 90) / degSize); const tileX = Math.floor((point.lng + 180) / degSize); return { tileX: tileX, tileY: tileY }; } function getTileId(tileXY, level) { return tileXY.tileY * 2 * Math.pow(2, level) + tileXY.tileX; } function splitArrayEqually(array, chunkSize) { const totalElements = array.length; const numOfChunks = Math.ceil(totalElements / chunkSize); const idealSize = Math.ceil(totalElements / numOfChunks); const result = []; for (let i = 0; i < numOfChunks; i++) { const start = i * idealSize; const end = start + idealSize; result.push(array.slice(start, Math.min(end, totalElements))); } return result; }}`;<br />
Notes on this pattern:
In the tile response, rows are found under Tiles[].Rows, and the layer name is read from Tiles[].Meta.layerName. This differs from the bounding box response structure.
The tileIds cache prevents the same tile from being requested again while the user pans.
The zoom check keeps requests to a single, predictable zoom level. Adjust or remove it to match your application.
### Choosing between the two options
| Use case | Recommended option |
| --- | --- |
| One-off extraction for a defined area | Bounding box |
| Interactive map where the user pans and zooms | Tile, with client-side caching |
| Simplest possible implementation | Bounding box |
| Lowest transaction count over a session | Tile |
Root Cause
----------
The Fleet Telematics extension classes in the HERE Maps API for JavaScript 3.1.x library are deprecated. As HERE platform services evolved, the Map Attributes API v8 became the supported way to retrieve map attribute data, and it is consumed directly as a REST service rather than through a JavaScript wrapper.
Impact
------
What continues to work
HERE Maps API for JavaScript 3.1.x for map rendering and view handling.
Direct calls to the Map Attributes API v8 endpoint from any client or server environment.
All supported map attribute layers, including speed limits and truck speed limits.
What may be affected
Applications relying on the deprecated H.service.extension Fleet Telematics classes.
Sample code copied from older projects or older documentation versions.
Implementations that assume a JavaScript service object exists for map attributes.
Recommended Actions
-------------------
Migration procedure
1. Identify all usage of the deprecated H.service.extension Fleet Telematics classes.
2. Decide whether bounding box or tile retrieval fits the application better.
3. Replace the deprecated calls with direct fetch() calls to https://smap.hereapi.com/v8/maps/attributes.
4. Request only the layers and functional classes that the use case requires.
5. Parse the response according to the mode used - geometries[].attributes for bounding box, Tiles[].Rows for tile.
6. Merge results per LINK_ID so attributes from different layers combine into one record per road segment.
7. Add client-side caching of already-fetched tiles if using tile mode.
8. Confirm your API key is enabled for the Map Attributes API and restricted appropriately for browser use.
Validation checklist
Requests return HTTP 200 with a JSON body.
meta=1 is present, so the layer name is available in the response.
Attributes appear for the expected road segments in the current view.
Merged records contain values from every requested layer where data exists.
Repeated panning does not re-request tiles that were already fetched.
No CORS or authentication errors appear in the browser developer tools.
Escalation guidance
Escalate only if:
The endpoint returns errors for valid, correctly encoded requests.
Authentication succeeds but a supported layer returns no rows for an area where data is expected.
Response structure does not match the documented format.
The same request produces inconsistent results across attempts.
Expected vs Unexpected Behavior
-------------------------------
| Expected behavior | Unexpected behavior |
| --- | --- |
| Requests return attribute rows for the requested area | Empty response for an area with known coverage |
| Layer name is available in the response when meta=1 is set | Rows cannot be matched back to their layer |
| Attributes merge cleanly per LINK_ID | Attributes overwrite each other across layers |
| Already-fetched tiles are skipped on pan | Same tiles requested repeatedly |
| Requests stay within supported layer and tile combinations | Errors when too many combinations are sent in one call |
Notes
-----
New implementations should not be based on the deprecated Fleet Telematics extension in the JavaScript library.
Layer availability and attribute coverage vary by region and by map release. Check the layer documentation for the layers you intend to use.
Splitting requests into batches, as shown in the tile sample, keeps each call within a manageable number of layer and tile combinations.
If the API key is used from a browser, restrict it to your application domains in the HERE platform.
The tile scheme used here is specific to the Map Attributes API and is not the same as the map display tile scheme.
Reference Info and Links
------------------------
HERE Map Attributes API v8 Developer Guide
Maps and layers in HERE Map Attributes API v8
Deprecated H.service.extension namespace reference
* HERE Maps API for JavaScript documentation
Keywords or Tags
----------------
Map Attributes API, Fleet Telematics API, smap.hereapi.com, map attributes v8, speed limits, SPEED_LIMITS, SPEED_LIMITS_COND, SPEED_LIMITS_VAR, TRUCK_SPEED_LIMITS, functional class, FC1 FC5, LINK_ID, bbox search, tile search, tile ID, H.service.extension, deprecated, HERE Maps API for JavaScript, JavaScript fetch, extract map data
Updated 3 days ago