--- title: "Map Streaming API Usage Guide" slug: "map-streaming-api-usage-guide" updated: 2026-11-03T16:42:31Z published: 2026-11-03T16:42:31Z canonical: "knowledge.navvis.com/map-streaming-api-usage-guide" --- > ## Documentation Index > Fetch the complete documentation index at: https://knowledge.navvis.com/llms.txt > Use this file to discover all available pages before exploring further. # Map Streaming API Usage Guide ## Overview Use this guide when you want to show the maps of an IVION site in a third-party application. You need access to an IVION instance with a site that contains maps. Follow this guide to download map tiles and place them in the site coordinate system, aligned with panoramas and point clouds. These are the floor maps that IVION generates from the scan data, not street-level base maps. > [!NOTE] > **Note:** > > - You download map files with pre-signed URLs from the IVION API, in the same way as point cloud files in the [Point Streaming API Usage Guide](/docs/en/point-streaming-api-usage-guide). > - Each map is stored in a single NVR archive that contains all of its tiles. See the [NVR File Format Specification](/docs/en/nvr-file-format-specification). > - The streaming APIs require API access, which is included in IVION Professional and IVION Enterprise but not in IVION Core. Without it, API requests fail with HTTP 403 and the message `API not licensed.` > - You can use the C++ [IVION Streaming API Sample Application](/docs/en/ivion-streaming-api-sample-application) as a reference in addition to this document. It shows maps, panoramas and point clouds together. > - All placeholders in this document use curly brackets {}. For example, {instance_url} For a description of all IVION API endpoints, see the [IVION API documentation](https://apidocs.navvis.com/). ## Logging in Third-party applications log in with a login mandate. The application requests a mandate, the user authorizes it in the IVION web application, and the application exchanges it for an access token and a refresh token. For more information, see the sections Logging In and Refreshing the Access Token in [Point Streaming API Usage Guide](/docs/en/point-streaming-api-usage-guide). All API requests in this guide require the header `X-Authorization: Bearer {access_token}`. Downloads with pre-signed URLs do not require it. > [!NOTE] > **Note:** > > Some IVION instances allow anonymous access. On such instances, you can skip the login: all requests in this guide, including the request for a pre-signed download URL, also work without the `X-Authorization` header, with the permissions of an anonymous user. ## Listing sites Retrieve the list of sites available to the user through the following endpoint: **Endpoint:** ```plaintext GET {instance_url}/api/sites X-Authorization: Bearer {access_token} ``` **Response Code & Status:** ```plaintext 200 OK ``` **Response Body:** ```json [  {    "id": number,    "name": string,    "description": string | null,    ...  },  ... ] ``` ## Listing maps Retrieve the maps of a site through the following endpoint: **Endpoint:** ```plaintext GET {instance_url}/api/site/{site_id}/tiled_maps X-Authorization: Bearer {access_token} ``` **Response Code & Status:** ```plaintext 200 OK ``` **Response Body:** ```json [  {    "id": number,    "scs_location": [number, number, number],    "scs_orientation": [number, number, number, number],    "storage_file_path": string,    "type": "TILED_PYRAMID",    "map_size_m": number,    "tile_size_px": number,    "max_depth": number,    "quadtree": string,    "outdoor": boolean,    "floor_id": number | null,    ...  },  ... ] ``` | **Attribute** | **Description** | | --- | --- | | scs_location | Center of the map (x, y, z) in the site coordinate system, in meters. The z coordinate is the lower height bound (`scs_z_min`) of the map's floor in the site model, or 0 for maps without a floor. | | scs_orientation | Orientation of the map in the site coordinate system as a quaternion in the order (w, x, y, z). | | storage_file_path | Storage path of the map's NVR archive, starting with the site ID. | | map_size_m | Edge length, in meters, of the square area covered by the root tile. | | tile_size_px | Edge length, in pixels, of a tile image. All tiles are square. | | max_depth | Deepest level of the tile pyramid. The root tile is at depth 0. | | quadtree | Describes which tiles exist. See the section Decoding the quadtree. | | outdoor | Whether the map shows the outdoor area of the site rather than a floor of a building. | | floor_id | ID of the site model floor the map belongs to. | Maps can overlap, for example when a site contains several scans of the same floor. Show outdoor maps (`outdoor` is true) together with the indoor maps. Show indoor maps per floor, or let the user switch individual maps on and off. ### Floor names To label maps, retrieve the site model of the site. It contains buildings with nested floors; match the floor's `id` with the map's `floor_id`. The fields `scs_z_min` and `scs_z_max` are the height range of a floor in the site coordinate system, in meters. **Endpoint:** ```plaintext GET {instance_url}/api/site/{site_id}/site_model X-Authorization: Bearer {access_token} ``` **Response Code & Status:** ```plaintext 200 OK ``` **Response Body:** ```json [  {    "id": number,    "type": "BUILDING",    "name": string,    "children": [      {        "id": number,        "type": "FLOOR",        "name": string,        "scs_z_min": number | null,        "scs_z_max": number | null,        ...      },      ...    ],    ...  },  ... ] ``` ## Requesting a pre-signed download URL Map files are downloaded with pre-signed URLs, in the same way as point cloud files. A pre-signed URL is scoped to a storage prefix and can be reused for all files under that prefix. **Endpoint:** ```plaintext POST {instance_url}/api/site/{site_id}/storage/download/prefix/signed/url X-Authorization: Bearer {access_token} ``` **Request Body:** ```json {  "prefix_in_site": "{prefix_in_site}",  "expiry_in_millis": number } ``` **Response Code & Status:** ```plaintext 200 OK ``` **Response Body:** ```json {  "url": string,  "issued_at_millis": number,  "expires_at_millis": number } ``` The field `expiry_in_millis` is optional. If you omit it, the instance's default validity is used, which is usually 5 minutes. The instance rejects requests for a longer validity than its maximum. To download a file, insert the file's storage path into the path of the returned URL, before the query string: ```plaintext {signed_base_url}/{storage_file_path}{signed_query_string} ``` > [!NOTE] > **Note:** > > - The storage path of a file starts with the site ID, for example `{site_id}/{directory}/{file_name}`. > - Monitor `expires_at_millis` and request a new URL before it expires. Do not rely on a fixed validity, because it depends on the instance configuration. > - Cache one signed URL per storage prefix. Avoid requesting several signed URLs for the same prefix in parallel. > - Usually, the URL points to a CDN. On instances without a CDN, the URL is a path on the IVION instance. If the URL starts with `/`, resolve it against the origin (scheme, host and port) of the instance. > - Signed URLs support HTTP Range requests. No `X-Authorization` header is needed. For a map, use the directory of its `storage_file_path` without the leading site ID as `prefix_in_site`. For example, if `storage_file_path` is `{site_id}/{directory}/{file_name}`, `prefix_in_site` is `{directory}`. The URL of the map's archive is then: ```plaintext {signed_base_url}/{storage_file_path}{signed_query_string} ``` ## Reading the NVR archive All tiles of a map are stored in a single NVR archive. The archive's index is stored at the end of the file, so a client can download any tile with an HTTP Range request after reading the index once. The format is described in the [NVR File Format Specification](/docs/en/nvr-file-format-specification). In short: 1. Request the last 2048 bytes of the archive with `Range: bytes=-2048`. 2. Read the 42-byte footer at the end: number of files `N` (`uint32`, big-endian, at offset 32) and archive type (`uint8`, at offset 36). 3. Read the index: `N` big-endian `uint32` file lengths directly before the footer. If the index does not fit into the first response, request the last `4 × N + 42` bytes. 4. The first file starts at byte 4. Each following file starts where the previous one ends. 5. Download a file with `Range: bytes={offset}-{offset + length - 1}`. Files with a length of 0 are missing. The archive type of a map archive is 2. Each file in the archive is the PNG image of one tile. The number of files equals the number of tiles in the quadtree. ## Tile pyramid A map is a pyramid of square tiles. The root tile at depth 0 covers the whole map, a square with an edge length of `map_size_m` meters. Each tile at depth `d` has up to four children at depth `d + 1`, each covering a quarter of the tile. Tiles are identified by depth `d` and column and row `(x, y)` within their depth; `(0, 0)` is the top-left tile, and `y` increases downwards in the image. The children of tile `(d, x, y)` are: | **Child** | **Position** | **Tile** | | --- | --- | --- | | 0 | Top-left | (d + 1, 2x, 2y) | | 1 | Top-right | (d + 1, 2x + 1, 2y) | | 2 | Bottom-left | (d + 1, 2x, 2y + 1) | | 3 | Bottom-right | (d + 1, 2x + 1, 2y + 1) | Only tiles that contain map content exist. The resolution of a tile at depth `d` is: ```plaintext meters_per_pixel = map_size_m / (tile_size_px × 2^d) ``` ### Decoding the quadtree The `quadtree` attribute is a string of hexadecimal digits. Each digit is the child mask of one tile: bit 0 (value 1) is set if child 0 exists, bit 1 (value 2) for child 1, bit 2 (value 4) for child 2 and bit 3 (value 8) for child 3. The digits describe the tiles in breadth-first order, starting with the root tile. If the string ends before all tiles are described, the remaining tiles have no children. The tiles are stored in the NVR archive in the same breadth-first order. Decode the quadtree with a queue to get each tile's file index: ```plaintext queue = [(0, 0, 0)]          # (depth, x, y) of the root tile next_digit = 0 file_index = 0 while queue is not empty:    (d, x, y) = queue.pop_front()    tile (d, x, y) is stored at file_index    file_index = file_index + 1    mask = hex value of quadtree[next_digit], or 0 if next_digit >= length(quadtree)    next_digit = next_digit + 1    for child in 0..3:        if mask has bit child set:            queue.push_back((d + 1, 2x + (child mod 2), 2y + (child div 2))) ``` An empty quadtree string means that the map contains no tiles. ## Positioning map tiles Map tiles are positioned in the map's local coordinate system: a plane in meters with the origin at the map center, +X pointing to the right of the map image and +Y to its top. The bounds of tile `(d, x, y)` are: ```plaintext size  = map_size_m / 2^d min_x = −map_size_m / 2 + x × size max_x = min_x + size max_y =  map_size_m / 2 − y × size min_y = max_y − size ``` The top row of the tile image is at `max_y`. To transform a point `(px, py)` from map-local coordinates into the site coordinate system, rotate it with the quaternion `q = scs_orientation` and add `scs_location`: ```plaintext site_point = scs_location + q × (px, py, 0) × q⁻¹ ``` For most maps, `scs_orientation` is the identity rotation (1, 0, 0, 0), so the map lies in a horizontal plane at the height `scs_location.z`. > [!NOTE] > **Site coordinates:** > > - Panorama poses and map poses are given in the site coordinate system, a right-handed metric coordinate system with Z pointing up. > - Point clouds are stored in dataset coordinates. To show them together with panoramas and maps, apply the dataset's site transformation, as described in the Applying Alignment section of the [Point Streaming API Usage Guide](/docs/en/point-streaming-api-usage-guide). ## Choosing the level of detail Load the root tile first, then refine: if a tile appears larger on screen than its `tile_size_px`, load its existing children and draw them instead of the tile. Keep drawing the parent tile until all of its existing children have loaded, so that no gaps appear while tiles stream in. Do not refine beyond `max_depth`. ## Panoramas To show panoramas on top of a map, see the [Panorama Streaming API Usage Guide](/docs/en/panorama-streaming-api-usage-guide). Panoramas use the same site coordinate system. ## FAQ #### What is the IVION Map Streaming API used for? With it, third-party applications can display the floor maps of an IVION site, aligned with panoramas and point clouds. #### Does the Map Streaming API include street maps? No. Street-level base maps are provided by third-party services and are not part of this API. #### Are map tiles in web mercator (z/x/y) coordinates? No. Map tiles use a local metric coordinate system per map, which is placed in the site coordinate system with the map's scs_location and scs_orientation. #### Why does a map have fewer files than a full pyramid would? Only tiles with map content exist. The quadtree describes which tiles exist and in which order they are stored.