---
title: Layers (weather)
source: https://infrared.city/docs/sdk/1.0/python/layers/
---

# Layers (weather)

Weather data and grid image generation.

## WeatherServiceClient

Bases: `ScrubbedSessionState`

Client for weather data and grid-image generation.

`static_base_url` selects the public catalog host. `api_key`, `base_url` and `gateway_base_url` are accepted for construction compatibility with the other service clients and are unused: the catalog is public, and no credential ever goes to that host.

### logger  `instance-attribute`

```python
logger: Logger = logger
```

Logger used for fetch, retry and debug messages.

### base_url  `instance-attribute`

```python
base_url: str = base_url
```

The service base URL passed at construction (unused by this client).

### close

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

Close the static reader's session.

### get_weather_file_from_location

```python
get_weather_file_from_location(
*, lat: Latitude, lon: Longitude, radius: Optional[int] = None
)
```

Return the nearest public weather stations, nearest first.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `lat` | `[Latitude](../models/#infrared_sdk.models.Latitude)` | Latitude of the point, in degrees. | *required* |
| `lon` | `[Longitude](../models/#infrared_sdk.models.Longitude)` | Longitude of the point, in degrees. | *required* |
| `radius` | `int` | Search radius in kilometres. Default 100. | `None` |

Returns:

| Type | Description |
| --- | --- |
| `list of dict` | Up to 10 catalog rows (station metadata), nearest first. |

Raises:

| Type | Description |
| --- | --- |
| `WeatherServiceError` | If the catalog cannot be fetched or decoded. |

### get_weather_file_from_identifier

```python
get_weather_file_from_identifier(*, identifier: str)
```

Return one public weather station's data by `uuid` or `fileName`.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `identifier` | `str` | The station's `uuid` or `fileName` in the public catalog. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `dict` | The station's data object. |

Raises:

| Type | Description |
| --- | --- |
| `WeatherServiceError` | If the identifier is not in the public catalog, or the catalog or station cannot be fetched or decoded. Private and custom-EPW files are not in the catalog; use `parse_epw` for those. |

### parse_epw

```python
parse_epw(source, **options)
```

Read one local `.epw` file and validate it.

The bring-your-own weather entry point, and the only one for a file that is not in the public catalog. Nothing is uploaded, nothing is registered and no request leaves this process. Pass the returned `WeatherDocument` straight to `from_weatherfile_payload` and keep it for the retry, because its `identity` is what proves a resume uses the same weather.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `source` | `(str, bytes, bytearray or PathLike)` | A path to the `.epw` file, or the file's text (a `str` with a line end, or `bytes`). | *required* |
| `**options` |  | `max_bytes` and `max_rows`: tighten the size and row bounds. See `parse_epw`. | `{}` |

Returns:

| Type | Description |
| --- | --- |
| `[WeatherDocument](#infrared_sdk.layers.WeatherDocument)` | The validated weather document. |

Raises:

| Type | Description |
| --- | --- |
| `[EpwParseError](#infrared_sdk.layers.EpwParseError)` | If the file is unreadable or not a usable EPW file. |

### filter_weather_data

```python
filter_weather_data(
*,
identifier: Optional[str] = None,
weather: Any = None,
time_period: TimePeriod,
)
```

Return weather filtered to `time_period`, one record per hour.

Give EITHER `identifier` (a public catalog station, by `uuid` or `fileName`) OR `weather` (a `WeatherDocument` from `parse_epw`). The BYO document is accepted everywhere a station id is, runs entirely on your machine and makes no request at all; the window means the same thing for both.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `identifier` | `str` | A public catalog station, by `uuid` or `fileName`. | `None` |
| `weather` | `[WeatherDocument](#infrared_sdk.layers.WeatherDocument)` | A document from `parse_epw`. | `None` |
| `time_period` | `[TimePeriod](../models/#infrared_sdk.models.TimePeriod)` | The window: a date span with a daily hour range. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `list of WeatherDataPoint` | One record per hour inside the window. |

Raises:

| Type | Description |
| --- | --- |
| `ValueError` | If neither or both of `identifier` and `weather` are given. |
| `WeatherServiceError` | For an `identifier`: if it is not in the public catalog, or the catalog or station cannot be fetched or decoded. |
| `[WeatherModelInputsError](#infrared_sdk.layers.WeatherModelInputsError)` | For `weather`: if the document cannot be filtered to the window. |

### gen_grid_image

```python
gen_grid_image(
*,
grid: Sequence[Sequence[Any]],
analysis_type: Optional[str] = None,
criteria: Optional[str] = None,
subtype: Optional[str] = None,
max_long_axis_px: Optional[int] = DEFAULT_MAX_LONG_AXIS_PX,
renderer: Any = REMOVED,
)
```

Render a result grid to a PNG and return the raw PNG bytes.

The grid is coloured with the public colour registry (`registry.infrared.city`; no API key, cached per process) for the analysis type you name. The image is **1:1, one pixel per grid cell**, up to a **960 px long axis**. A failure raises `WeatherServiceError`; you never get a fallback-coloured image when the registry colours cannot be loaded.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `grid` | `sequence of sequence` | Rows of cells: numbers, `None` (no data), or wind-comfort class labels (`"A"`..`"E"`, `"S"`, `"S15"`, `"S20"`). | *required* |
| `analysis_type` | `str` | Registry analysis type used to pick the colours. Omit it to colour the grid with the default `magma_r` ramp. | `None` |
| `criteria` | `str` | Criteria key for an analysis type with several variants. | `None` |
| `subtype` | `str` | Subtype key for an analysis type with several subtypes. | `None` |
| `max_long_axis_px` | `int` | Cap on the long axis of the image, in pixels. Default 960. A larger grid is sampled nearest-neighbour on the VALUES, so it keeps its aspect ratio and its no-data cells; `None` or `0` renders every cell. Read the image size and the scale factor with `infrared_sdk.layers.image_local.grid_image_size`. | `DEFAULT_MAX_LONG_AXIS_PX` |
| `renderer` | `Any` | Removed. Passing it raises `TypeError`. | `REMOVED` |

Returns:

| Type | Description |
| --- | --- |
| `bytes` | The PNG file contents. |

Raises:

| Type | Description |
| --- | --- |
| `WeatherServiceError` | If the grid is malformed, the colour registry cannot be loaded, or rendering fails. |

## EpwParseError

Bases: `ValueError`

An EPW file that was refused, with the reason.

A `ValueError` subclass, so a caller that already catches `ValueError` around `parse_epw` keeps working. The named type is what lets a caller separate a bad FILE from a bad request.

## WeatherDocument

One validated EPW file.

Hold it, pass it to a payload builder, and keep it for the retry: its `identity` is what proves a resumed run uses the same weather as the run it resumes.

Warnings

The identity is computed on every read, never cached. The document is a plain dict you can reach and change, and if you edit it after submitting, the retry is refused rather than served a stale digest that says the weather is unchanged. Computing it hashes the weather columns; it does not re-parse the file.

Attributes:

| Name | Type | Description |
| --- | --- | --- |
| `document` | `dict` | The parsed file as plain data (`schema`, `location`, `period`, `time`, `fields`, `values`, `missing` and `identity`), so it round-trips through `json`. |
| `source_path` | `str or None` | Where the bytes came from, used in messages. It is never hashed: two copies of one file under two names are the same weather. |

### identity  `property`

```python
identity: str
```

The versioned weather identity, `"sha256:<hex>"`.

It covers the validated values, their order, the location, the calendar columns and the hour basis, and never the file name, a station id or the raw text, so two differently formatted files with the same readings share one identity.

Raises:

| Type | Description |
| --- | --- |
| `[EpwParseError](#infrared_sdk.layers.EpwParseError)` | If the document is too malformed to have an identity. |

### location  `property`

```python
location: Mapping[str, Any]
```

The EPW `LOCATION` header, as the document records it.

### period  `property`

```python
period: Mapping[str, Any]
```

Row count, records per hour, leap-year and full-year verdicts.

### rows  `property`

```python
rows: int
```

Data rows in the file, before any window is applied.

### to_dict

```python
to_dict() -> Dict[str, Any]
```

Return the document itself, as plain data.

Serialise it with `json.dumps`.

### filter_hours

```python
filter_hours(time_period: TimePeriod) -> List[WeatherDataPoint]
```

Return the file's hours inside `time_period`, one record per hour.

The same shape `WeatherServiceClient.filter_weather_data` returns for a public station, from a local file and with no network call. The window is a date span with a daily hour range, exactly the hours the model counts. A window that crosses the year end (for example 1 December to 28 February) keeps its hours in calendar order of the year: January, February, then December.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `time_period` | `[TimePeriod](../models/#infrared_sdk.models.TimePeriod)` | The analysis window. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `list of WeatherDataPoint` | One record per hour inside the window. |

Raises:

| Type | Description |
| --- | --- |
| `[WeatherModelInputsError](#infrared_sdk.layers.WeatherModelInputsError)` | If the file cannot be filtered to that window. |

### model_inputs

```python
model_inputs(
*,
analysis_type: str,
time_period: TimePeriod,
subtype: Optional[str] = None,
solar_model: Optional[str] = None,
) -> Dict[str, Any]
```

Return the snake_case weather arrays one model reads, for one window.

The window is applied first, then the required fields are checked on the FILTERED rows, then the period rule, so a gap outside the window is harmless, a gap inside it raises, and a model that needs a full year raises on a shorter one.

`solar_model` belongs to `analysis_type="energy-balance"`, the one analysis that reads it; on any other analysis it raises, because nothing would read it there. `solar_model="irradiance"` selects the interior-irradiance input set and its full-year rule; omitted, or `"legacy-flat"`, selects the two climate arrays that model reads.

To RUN energy-balance with a file, use `from_weather`, which calls this method with the full-year window and the request's own `solar_model`.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `analysis_type` | `str` | The analysis type, e.g. `"thermal-comfort-index"`. | *required* |
| `time_period` | `[TimePeriod](../models/#infrared_sdk.models.TimePeriod)` | The analysis window. | *required* |
| `subtype` | `str` | The analysis subtype, for analyses that have several. | `None` |
| `solar_model` | `str` | `"legacy-flat"` or `"irradiance"`; only valid for `analysis_type="energy-balance"`. | `None` |

Returns:

| Type | Description |
| --- | --- |
| `dict` | The weather arrays the model reads, keyed by snake_case field name. |

Raises:

| Type | Description |
| --- | --- |
| `[WeatherModelInputsError](#infrared_sdk.layers.WeatherModelInputsError)` | If the document cannot supply what the model needs for this window. |

## WeatherModelInputsError

Bases: `ValueError`

The document cannot supply what the requested model needs.

Raised for a gap in a required column inside the selected window, for a window shorter than a full year where the model needs one, and for an analysis that has no weather input set. Always raised before submission.

## parse_epw

```python
parse_epw(
source: Union[str, bytes, bytearray, PathLike],
*,
max_bytes: Optional[int] = None,
max_rows: Optional[int] = None,
) -> WeatherDocument
```

Read and validate one `.epw` file, and return its document.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `source` | `(str, bytes, bytearray or PathLike)` | A path (`str` without a line end, or `Path`), or the file's text (`str` with a line end, or `bytes`). | *required* |
| `max_bytes` | `int` | Lower the built-in size bound (16 MiB). It may only be TIGHTENED; asking for a wider bound raises rather than being ignored. | `None` |
| `max_rows` | `int` | Lower the built-in row bound (8784 rows). It may only be TIGHTENED; asking for a wider bound raises rather than being ignored. | `None` |

Returns:

| Type | Description |
| --- | --- |
| `[WeatherDocument](#infrared_sdk.layers.WeatherDocument)` | The validated document, ready for `from_weatherfile_payload`. |

Raises:

| Type | Description |
| --- | --- |
| `[EpwParseError](#infrared_sdk.layers.EpwParseError)` | For every file that is refused: no `LOCATION` header, a location number out of range, sub-hourly data, a row shorter than 22 columns, an hour outside 1-24, a calendar cell that is not a number, or a file over the size or row bound. The message names the row or the field. |
