---
title: Buildings
source: https://infrared.city/docs/sdk/1.0/python/buildings/
---

# Buildings

Buildings service and data types.

## BuildingsAcquisitionError

Bases: `Exception`

Direct building acquisition failed in a way the caller must see.

## BuildingsServiceClient

Bases: `ScrubbedSessionState`

Client for 3D building meshes, read from the public sources.

`api_key`, `base_url`, `gateway_base_url` and `telemetry` are accepted for construction compatibility with the other service clients and are unused: this client sends no request of its own. It implements the context-manager protocol.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `api_key` | `str` | API key, accepted for compatibility with the other service clients. | *required* |
| `logger` | `Logger` | Logger used for progress and warning messages. | *required* |
| `base_url` | `str` | Base URL, accepted for compatibility with the other service clients. | *required* |
| `gateway_base_url` | `str` | Accepted for compatibility and unused. | `None` |
| `telemetry` | `Telemetry` | Accepted for compatibility and unused. | `None` |
| `acquisition` | `optional` | Removed option. Passing any value raises `TypeError`. | `REMOVED` |

Attributes:

| Name | Type | Description |
| --- | --- | --- |
| `logger` | `Logger` | Logger used for progress and warning messages. |
| `base_url` | `str` | Base URL the client was constructed with. |

### close

```python
close() -> None
```

Release the client's resources. Kept for API compatibility.

### get_area

```python
get_area(
polygon: dict,
*,
config: Optional[BuildingsConfig] = None,
on_progress: Optional[Callable[[TileProgress], None]] = None,
max_tiles_override: Optional[int] = None,
timeout: int = 60,
total_timeout: int = 600,
max_workers: int = DEFAULT_TILE_ACQUISITION_WORKERS,
overture_release: Optional[str] = None,
analysis_type: Optional[str] = None,
acquisition: Any = REMOVED,
) -> AreaBuildings
```

Fetch and deduplicate buildings for a polygon area.

The whole site is read at once: a single rectangle up to 4 km2, and a grid of roughly 2 x 2 km read chunks above it, at most two in flight. The meshes come back in one site frame, anchored at the polygon bounding-box south-west corner, and `run_area` re-anchors them per tile. A polygon straddling a registered city's outline is read tile by tile instead, because a site-level read would give the city's bodies to the tiles outside it.

There is no partial answer on the site path. A chunk that fails raises `TiledRunError`, whose `failed_tiles` names the failed chunks and whose `__cause__` is the first underlying error.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `polygon` | `dict` | GeoJSON Polygon. | *required* |
| `config` | `[BuildingsConfig](#infrared_sdk.buildings.BuildingsConfig)` | Accepted for compatibility and unused: it shaped the removed `POST /buildings` body. | `None` |
| `on_progress` | `callable` | Progress callback `(TileProgress) -> None`, once per read CHUNK on the site path. `tile_id` is the chunk id and `total_count` the chunk count. | `None` |
| `max_tiles_override` | `int` | Override the maximum number of non-empty tiles allowed. | `None` |
| `timeout` | `int` | Per-REQUEST budget in seconds (default 60), trimmed to what is left of `total_timeout`. | `60` |
| `total_timeout` | `int` | Wall-clock deadline over the whole site read, in seconds (default 600). | `600` |
| `max_workers` | `int` | Read chunks in flight on the site path. A CEILING of 2 applies, so this can only ask for FEWER. On the mixed-source per-tile path it is still the thread count. | `DEFAULT_TILE_ACQUISITION_WORKERS` |
| `overture_release` | `str` | Pin the Overture footprints to one immutable release. The resolved release is reported on the result. | `None` |
| `analysis_type` | `str` | The analysis this acquisition is for. It decides the tile grid and the READ MARGIN, the half extent of each read rectangle: 256 m for the two wind analyses and 384 m for every other one, which needs the shadow casters further out. Omit it for the widest margin, valid for every analysis. | `None` |

Returns:

| Type | Description |
| --- | --- |
| `[AreaBuildings](#infrared_sdk.buildings.AreaBuildings)` | Deduplicated buildings with coordinates in polygon-bbox-SW meter space, and `origin` naming that frame — pass the whole object to `run_area(buildings=…)` and it re-anchors from it, so one acquisition can serve several sub-areas. `building_ids` is always empty: the read path has no numeric building ids. `failed_tiles` is empty on the site path: a read that could not cover the polygon raises instead. |

Raises:

| Type | Description |
| --- | --- |
| `PolygonValidationError` | If `polygon` is not a valid GeoJSON Polygon, or covers more non-empty tiles than allowed (see `max_tiles_override`). |
| `TiledRunError` | If a read chunk fails on the site path. |
| `TypeError` | If the removed `acquisition` argument is passed. |

### get_by_tiles

```python
get_by_tiles(
tiles: TileGrid,
*,
config: Optional[BuildingsConfig] = None,
on_progress: Optional[Any] = None,
timeout: int = 60,
total_timeout: int = 600,
max_workers: int = DEFAULT_TILE_ACQUISITION_WORKERS,
) -> Dict[str, Optional[dict]]
```

Fetch buildings for each non-empty tile in a pre-generated grid.

Returns a per-tile mapping `{tile_id: buildings_dict | None}` intended for inspection or manual per-tile dispatch.

All tile IDs from the grid are present as keys in the returned dict. A `None` value means the fetch failed for that tile (distinguishable from "no buildings found", which would be an empty dict).

To combine `get_by_tiles` output into a single dict for visualisation (not for `run_area`) see `merge_buildings`.

Warnings

This shape is NOT a drop-in for `InfraredClient.run_area`'s `buildings=` kwarg, which expects a flat `{building_key: building_data}` mapping (each value a dict with top-level `coordinates`, `mesh_id`, `indices`). Passing this method's per-tile output directly into `run_area(buildings=...)` will fail every tile with `"missing or non-sequence coordinates"`. For the area pipeline use `client.buildings.get_area(polygon).buildings`, the canonical flat shape.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `tiles` | `[TileGrid](../tiling/#infrared_sdk.tiling.TileGrid)` | Tile grid produced by tile generation for a polygon. | *required* |
| `config` | `[BuildingsConfig](#infrared_sdk.buildings.BuildingsConfig)` | Accepted for compatibility and unused: it shaped the removed `POST /buildings` body. | `None` |
| `on_progress` | `callable` | Progress callback `(TileProgress) -> None`. | `None` |
| `timeout` | `int` | Per-tile timeout in seconds (default 60). | `60` |
| `total_timeout` | `int` | Total wall-clock timeout in seconds (default 600). | `600` |
| `max_workers` | `int` | Maximum parallel threads for tile execution (default 20). | `DEFAULT_TILE_ACQUISITION_WORKERS` |

Returns:

| Type | Description |
| --- | --- |
| `dict[str, dict \| None]` | Maps each non-empty tile's ID to its buildings dict, or `None` if the fetch failed for that tile. |

## AreaBuildings

Bases: `[Payload](../models/#infrared_sdk.models.Payload)`

Result of `get_area()`.

Frozen Pydantic model — field reassignment is blocked, but mutable containers (`buildings` dict) can still be mutated in-place (Pydantic v2 behavior), enabling the edit-before-run workflow.

Attributes:

| Name | Type | Description |
| --- | --- | --- |
| `buildings` | `dict[str, [DotBimMesh](#infrared_sdk.buildings.DotBimMesh)]` | Building meshes keyed by building identifier. |
| `attributes` | `dict[str, dict]` | `{height, min_height, num_floors, source_id}` per building, under the same keys as `buildings`. Populated on both read paths (the site-level read and the per-tile read a mixed-source polygon falls back to). Empty when the meshes did not come from a direct read, which carries meshes and no attributes. Empty therefore means "not available on this path", never "this building has no height". |
| `building_ids` | `list[int]` | Numeric building IDs from the API response. |
| `polygon` | `dict` | The input GeoJSON polygon. |
| `total_buildings` | `int` | Number of buildings in `buildings`. |
| `execution_time` | `float` | Wall-clock seconds for the fetch. |
| `failed_tiles` | `list[dict]` | Per-tile failure records for tiles that did not produce buildings after all retries (empty when every tile succeeded). Each entry has `tile_id`, `row`, `col`, `error`. Callers can use `len(area.failed_tiles)` to detect partial-coverage results. |
| `overture_release` | `(str, optional)` | Overture Maps release the footprints were read from, when the direct acquisition path produced them (`None` on the service path, which does not report one). The public index pointer moves daily; pass the value back as `overture_release=` to pin a later run to the same snapshot. |
| `read_margin_m` | `(float, optional)` | Half extent, in metres, of the read rectangle every tile was fetched with: 256 m for the wind analyses, 384 m otherwise (see `analysis_type`). `run_area` refuses a run whose analysis needs more than this. |
| `analysis_type` | `(str, optional)` | The analysis type the read margin was taken from. `None` on an object built by hand, which makes no claim and is never refused. |
| `origin` | `(tuple, optional)` | `(lon, lat)` of the site frame the meshes are stored in: the `polygon` bounding-box south-west corner. `run_area(buildings=<this object>)` reads it and re-anchors from THIS frame, so acquiring once for a large polygon and running several sub-areas places the buildings correctly. A bare `{id: mesh}` map carries no origin and is read as being in the run polygon's own frame, which is the behaviour that has always applied. |

## BuildingsConfig

Bases: `CamelCasePayload`

Configuration for tiled buildings retrieval.

Accepted for compatibility and unused: it shaped the removed buildings request.

Attributes:

| Name | Type | Description |
| --- | --- | --- |
| `output_format` | `OutputFormat` | Desired output format (default DotBim). |
| `compress` | `bool` | Whether to request compressed responses. |
| `optimizations` | `(BuildingOptimizations, optional)` | Optimizations of the removed per-tile request; no effect. |

## BuildingsRequest

Bases: `CamelCasePayload`

Request body for a buildings query.

Kept for compatibility: the SDK no longer sends this request.

Attributes:

| Name | Type | Description |
| --- | --- | --- |
| `coordinates` | `BuildingCoordinates` | Centre of the query. |
| `size` | `BuildingSize` | Extent of the query. |
| `return_building_ids` | `bool` | Whether to return numeric building IDs (default `False`). |
| `output_format` | `OutputFormat` | Desired output format (default DotBim). |
| `compress` | `bool` | Whether to request a compressed response (default `False`). |
| `optimizations` | `(BuildingOptimizations, optional)` | Optimisations to apply. |

## DotBimMesh

Bases: `[Payload](../models/#infrared_sdk.models.Payload)`

A single DotBim mesh entry from the buildings API.

Follows the `Payload` base pattern (frozen, kebab-case aliases).

Attributes:

| Name | Type | Description |
| --- | --- | --- |
| `mesh_id` | `int` | Numeric identifier of the mesh. |
| `coordinates` | `list[float]` | Flat `[x, y, z, ...]` coordinate array. |
| `indices` | `list[int]` | Triangle index array. Required. |

Notes

`indices` is required. A mesh without it is silently dropped by the simulation service before any computation runs: no error, no warning, and a tile that reads as if the building had never been submitted, yet is billed in full. The SDK therefore refuses to build one.

## merge_buildings

```python
merge_buildings(buildings_map: Dict[str, Optional[dict]]) -> dict
```

Combine per-tile buildings dicts into one for visualization.

Merges all non-None values from *buildings_map* into a single dict. Each key in the individual buildings dicts whose value is a list is concatenated; other keys are taken from the last dict encountered.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `buildings_map` | `dict[str, dict \| None]` | Maps `tile_id -> buildings dict` (or `None`). | *required* |

Returns:

| Type | Description |
| --- | --- |
| `dict` | Combined buildings dict, or empty dict if all are `None`. |
