Skip to content
View as Markdown llms.txt

Client

Infrared SDK client.

Provides InfraredClient, the main entry point for the Infrared City API. Supports area-level tiled orchestration, webhook integration, and building/vegetation/ground-material queries.

BASE_URL_ENV_NAME module-attribute

BASE_URL_ENV_NAME = 'INFRARED_BASE_URL'

Name of the environment variable that supplies the API base URL.

API_KEY_ENV_NAME module-attribute

API_KEY_ENV_NAME = 'INFRARED_API_KEY'

Name of the environment variable that supplies the API key.

DEFAULT_BASE_URL module-attribute

DEFAULT_BASE_URL = 'https://api.infrared.city/v2'

API base URL used when no valid base_url is given.

InfraredClient

Main client for the Infrared City API.

Supports area-level orchestration via run_area / run_area_and_wait, and webhook management via self.webhooks.

Resources for agents and integrators: the SDK documentation at https://infrared.city/docs/sdk/, and the agent skills (Claude Code / Cursor / Codex / Copilot / Windsurf) and runnable cookbook notebooks in https://github.com/Infrared-city/infrared-skills.

A single INFO log line on first instantiation per process surfaces these links to debug sessions. Set INFRARED_QUIET=1 to silence.

Identifying your application

Every outbound request carries two telemetry headers so Infrared can tell which client made a call. Downstream integrators — plugins, connectors, other SDKs — should identify themselves:

  • application → x-infrared-application: the calling SURFACE ("qgis", "arcgis", "sketchup", …). Defaults to "sdk". Any value is accepted; the gateway owns the list of known surfaces and records unrecognised ones as other.
  • sdk_id → x-infrared-sdk: the calling LIBRARY and ITS OWN version, e.g. "infrared-qgis/1.1.2". This SDK's own token is appended rather than replaced, so the wire carries "infrared-qgis/1.1.2 infrared-sdk/<version>" — the host is identified without losing which SDK version ran.

::

client = InfraredClient(
    api_key=...,
    application="qgis",
    sdk_id=f"infrared-qgis/{plugin_version}",
)

Both resolve as argument → environment variable → default, with INFRARED_APPLICATION / INFRARED_SDK as the env fallbacks for hosts that build the client through glue code and cannot pass arguments. A malformed value (blank, or carrying a control character — the header-injection case) raises ValueError here rather than failing on the first API call. Neither value can reach any header other than the two above, so neither can shadow x-api-key.

Parameters:

Name Type Description Default
api_key str

API key. When omitted it is read from the INFRARED_API_KEY environment variable.

None
logger Logger

Logger for client messages. Defaults to this module's logger.

None
base_url str

Absolute http(s) URL of the API, for example "https://api.infrared.city/v2". Resolved as argument, then the INFRARED_BASE_URL environment variable, then the default "https://api.infrared.city/v2". A value that is not an absolute http(s) URL is ignored with a warning and the next source is used. A trailing / is removed.

None
transport (json, binary)

Default request representation for runs started by this client. None (default) lets each run choose: binary, except for analyses that have no binary route, which use JSON. A retry (retry_from) keeps the transport of the schedule it resumes. A transport argument to a run method overrides this value.

"json"
execution_config ExecutionConfig

Execution mode. ExecutionConfig.exec_async (default) is the only mode.

exec_async
application str

Calling surface reported in x-infrared-application (see above). Defaults to "sdk".

None
sdk_id str

Calling library and version reported in x-infrared-sdk (see above).

None
jobs_service_client JobsServiceClient

Job client to use instead of the one this client creates. A client you inject is not closed by close.

None
analysis_service_client AnalysisServiceClient

Analysis client to use instead of the default.

None
weather_service_client WeatherServiceClient

Weather client to use instead of the default.

None
vegetation_service_client VegetationServiceClient

Vegetation client to use instead of the default.

None
ground_materials_service_client GroundMaterialsServiceClient

Ground-material client to use instead of the default.

None
landuse_service_client LandUseServiceClient

Land-use client to use instead of the default.

None
buildings_service_client BuildingsServiceClient

Buildings client to use instead of the default.

None
webhooks_service_client WebhooksServiceClient

Webhooks client to use instead of the default.

None

Attributes:

Name Type Description
api_key _SharedApiKey

Holder of the API key. Copying or pickling the client drops the key; create a new client instead of reusing a copy.

telemetry Telemetry

The resolved application and sdk_id header values.

execution_config ExecutionConfig

Execution mode.

transport str or None

Default transport, or None when each run chooses.

base_url str

The resolved API base URL, without a trailing /.

jobs JobsServiceClient

Job submission, status and result access.

analyses AnalysisServiceClient

Single-tile analysis execution.

weather WeatherServiceClient

Weather catalog access.

vegetation VegetationServiceClient

Vegetation (tree) data access.

ground_materials GroundMaterialsServiceClient

Ground-material data access.

landuse LandUseServiceClient

Land-use data access.

buildings BuildingsServiceClient

Building data access.

webhooks WebhooksServiceClient

Webhook management.

Raises:

Type Description
ValueError

If no API key is given and INFRARED_API_KEY is not set, if application or sdk_id is malformed, or if transport is not "json" or "binary".

Notes

An injected service client (the *_service_client arguments) keeps whatever telemetry it was constructed with on its own session, the same contract as its api_key. The area paths (run_area, merge_area_jobs, check_area_state) build their own per-thread clients and use the client-level value resolved here, not the injected client's. So an injected jobs_service_client carrying a different application reports its own on client.jobs.submit() and this client's on an area run. Pass application / sdk_id here rather than pre-labelling an injected client if you want one value everywhere.

close

close() -> None

Close the resources this client owns (sessions it created itself).

Service clients passed in through the *_service_client arguments are left open.

preview_area

preview_area(
    polygon: dict,
    max_tiles_override: Optional[int] = None,
    analysis_type: Optional[str] = None,
    *,
    payload: Optional[AnalysesUnion] = None,
    buildings: Optional[Mapping[str, dict]] = None,
    vegetation: Optional[Mapping[str, dict]] = None,
    ground_materials: Optional[Mapping[str, dict]] = None,
    max_sensors_per_job: Optional[float] = None,
    terrain_context_margin_m: Optional[float] = None,
) -> AreaPreview

Preview tiling for a polygon without running any analyses.

Examples:

# Wrong for solar — uses wind grid (256 m step)
preview = client.preview_area(polygon)

# Right — solar-radiation grid (512 m step)
preview = client.preview_area(
    polygon, analysis_type="solar-radiation"
)

Wire-format analysis names (kebab-case, matching the API): "wind-speed", "pedestrian-wind-comfort", "solar-radiation", "direct-sun-hours", "daylight-availability", "sky-view-factors", "thermal-comfort-index", "thermal-comfort-statistics".

Warnings

The default grid is wind (256 m step). Calling preview_area(polygon) with no analysis_type returns the wind-grid tile count for backwards compatibility. Solar / daylight / thermal-comfort analyses run on a 512 m grid (~4x fewer tiles per area), so the default preview over-counts tiles by ~4x and under-estimates cost for solar-family workflows. Always pass analysis_type when you know which analysis you will run, with the same value your run_area payload uses, so the estimate matches what the run actually submits (and charges). Omitting analysis_type emits a UserWarning at runtime.

A facade estimate needs payload= (and usually buildings=). Requests with analysis_surfaces set are transparently split into multiple separately billed sub-jobs when a tile's estimated synthesized sensor count exceeds the server cap. Without payload=, this preview has no geometry to split and reports one job per tile -- an UNDER-estimate for a facade run. Pass the real payload (and buildings) to get the real count. See the README's "Facade & Terrain Analysis" section. Note that a repeated facade run still bills in full even when nothing on the client changed.

Parameters:

Name Type Description Default
polygon dict

A GeoJSON Polygon object.

required
max_tiles_override int

Override the default maximum number of non-empty tiles.

None
analysis_type str

Wire-format analysis name (see warning above). Selects the tile grid: wind types use a 256 m step (50 % overlap); solar / daylight / thermal-comfort types use a 512 m step (no overlap, ~4x fewer tiles per area). None (default) falls back to the wind grid for legacy callers — explicitly pass the analysis you intend to run for an accurate preview. When payload is given, payload.analysis_type decides the grid (matching run_area, which takes no separate analysis_type at all) and this parameter is ignored.

None
payload AnalysesUnion

The SAME payload you would pass to run_area. Required for an accurate facade (analysis_surfaces) estimate — see the warning above. Its analysis_type selects the tile grid (see analysis_type above); otherwise ignored for pricing beyond selecting the grid and (with analysis_surfaces set) running the offline batch split; nothing here is submitted.

None
buildings mapping

The same per-analysis layers run_area accepts. Only read when payload carries analysis_surfaces — a facade batch count depends on which buildings land in which tile.

None
vegetation mapping

The same per-analysis layers run_area accepts. Only read when payload carries analysis_surfaces — a facade batch count depends on which buildings land in which tile.

None
ground_materials mapping

The same per-analysis layers run_area accepts. Only read when payload carries analysis_surfaces — a facade batch count depends on which buildings land in which tile.

None
max_sensors_per_job float

Per-job sensor cap for a facade payload, in retained sensors: a whole number from 1 to 250 000. A fractional value is rounded down with a DeprecationWarning. Passed to the same offline batch split run_area uses, so the estimate matches a real run built with the same cap.

None
terrain_context_margin_m float

Terrain reach in metres, as for run_area. Only used for the facade batch split.

None

Returns:

Type Description
AreaPreview

tile_count (int): number of non-empty tiles on the grid for analysis_type. would_bill_jobs (int): the planned job count -- equal to tile_count for a grid analysis, and the real (higher) count of facade sub-batches when payload carries analysis_surfaces. Price from this field, not tile_count. sensor_count (int or None): total synthesized sensors across the planned facade batches, or None off a grid preview. estimated_time_s / estimated_cost_tokens: scaled by would_bill_jobs for one analysis at that grid. Multi-analysis workflows must multiply by the number of analyses on the same grid family.

Raises:

Type Description
PolygonValidationError

If polygon is not a valid GeoJSON polygon, if max_tiles_override is not a non-negative integer, or if the polygon produces more tiles than the limit. It is a subclass of ValueError.

ValueError

If max_sensors_per_job or terrain_context_margin_m is not a valid number, or if a supplied layer is invalid.

Warns:

Type Description
UserWarning

If neither analysis_type nor payload is given, because the preview then uses the wind grid.

merge_buildings staticmethod

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

Combine per-tile buildings dicts into one for visualization.

Parameters:

Name Type Description Default
buildings_map dict[str, dict or None]

Maps tile id to that tile's buildings dict, or None.

required

Returns:

Type Description
dict

The combined buildings dict: list values are concatenated and other keys are taken from the last dict that has them. An empty dict if every value is None.

run_area

run_area(
    payload: AnalysesUnion,
    polygon: dict,
    *,
    buildings: Optional[
        Union[AreaBuildings, Mapping[str, Union[DotBimMesh, dict]]]
    ] = None,
    vegetation: Optional[Mapping[str, dict]] = None,
    ground_materials: Optional[Mapping[str, dict]] = None,
    max_tiles_override: Optional[int] = None,
    max_workers: int = DEFAULT_SUBMIT_WORKERS,
    webhook_url: Optional[str] = None,
    webhook_events: Optional[List[str]] = None,
    retry_from: Optional[AreaSchedule] = None,
    known: Optional[Dict[str, Any]] = None,
    max_sensors_per_job: Optional[int] = None,
    terrain_context_margin_m: Optional[float] = None,
    transport: Optional[str] = None,
    on_accepted: Optional[Callable[[str, str], None]] = None,
) -> AreaSchedule
run_area(
    payload: List[AnalysesUnion],
    polygon: dict,
    *,
    buildings: Optional[
        Union[AreaBuildings, Mapping[str, Union[DotBimMesh, dict]]]
    ] = None,
    vegetation: Optional[Mapping[str, dict]] = None,
    ground_materials: Optional[Mapping[str, dict]] = None,
    max_tiles_override: Optional[int] = None,
    max_workers: int = DEFAULT_SUBMIT_WORKERS,
    webhook_url: Optional[str] = None,
    webhook_events: Optional[List[str]] = None,
    retry_from: Optional[AreaSchedule] = None,
    known: Optional[Dict[str, Any]] = None,
    max_sensors_per_job: Optional[int] = None,
    terrain_context_margin_m: Optional[float] = None,
    transport: Optional[str] = None,
    on_accepted: Optional[Callable[[str, str], None]] = None,
) -> List[AreaSchedule]
run_area(
    payload: Union[AnalysesUnion, List[AnalysesUnion]],
    polygon: dict,
    *,
    buildings: Optional[
        Union[AreaBuildings, Mapping[str, Union[DotBimMesh, dict]]]
    ] = None,
    vegetation: Optional[Mapping[str, dict]] = None,
    ground_materials: Optional[Mapping[str, dict]] = None,
    max_tiles_override: Optional[int] = None,
    max_workers: int = DEFAULT_SUBMIT_WORKERS,
    webhook_url: Optional[str] = None,
    webhook_events: Optional[List[str]] = None,
    retry_from: Optional[AreaSchedule] = None,
    known: Optional[Dict[str, Any]] = None,
    max_sensors_per_job: Optional[int] = None,
    terrain_context_margin_m: Optional[float] = None,
    transport: Optional[str] = None,
    on_accepted: Optional[Callable[[str, str], None]] = None,
) -> Union[AreaSchedule, List[AreaSchedule]]

Submit tiled analysis jobs over a polygon.

max_workers sizes the submit pool: how many tile POSTs are in flight at once. The default is DEFAULT_SUBMIT_WORKERS (8), where throughput measurably stops improving; a caller who has measured their own workload may ask for up to MAX_SUBMIT_WORKERS (20). It is NOT the merge pool — merge_area_jobs sizes that one separately — and it does nothing for the first few seconds of a run, which are serial layer preparation.

Layer parameters (buildings, vegetation, ground_materials): None or {} skips injection (empty per tile), a non-empty mapping uses the provided data. Do not mutate the layer mappings while a call runs. buildings also accepts the AreaBuildings object buildings.get_area returns, and then re-anchors from the frame that object RECORDS — so acquiring once for a large polygon and running several sub-areas places the buildings correctly. A bare map carries no frame and is read as being in the run polygon's own, which is why the README says to acquire with the polygon you run.

buildings entries must carry both coordinates and indices — as DotBimMesh or as a dict; the server discards a mesh missing either, before compute and after the charge, so one is refused here. vegetation features may be Point, Polygon or MultiPolygon; a polygon is seated at its outer ring's centroid. Any other vegetation geometry type raises.

retry_from (resubmit only the failed tiles/batches of a prior run, carrying the succeeded jobs forward) resubmits failed_submissions only; uncertain_submissions (a tile whose request may already have created a job) are carried forward untouched and never resubmitted. retry_from describes exactly one payload's prior run, so it is not supported with a multi-payload list: run_area([A, B], polygon, retry_from=schedule_B) raises ValueError. Retry each payload separately — run_area(B, polygon, retry_from=schedule_B). The retry payload must also match the original run's config_hash and buildings map (both guarded — a mismatch raises).

terrain_context_margin_m widens how far ground_geometry is sliced beyond each tile. The default (None) covers the buildings and trees the tile actually analyses and nothing more. This extent is independent of terrain_alignment: the SDK's supplied-scene default is as-is and does not seat or validate those solids. Long-range relief belongs in context_geometry. Raise it only for a site where distant terrain genuinely shades the tile (a valley, an escarpment); the payload grows roughly with the square of the reach. Values below the tile config's own context margin are floored to it, so this can never strand a building or tree that was admitted to the tile on absent ground.

An interrupt (KeyboardInterrupt, SystemExit, a timeout that cancels the calling thread) part-way through submission still propagates -- this method never swallows it, and never cancels a tile already accepted server-side. It DOES stop a tile the submit pool had not yet reached: nothing new goes out over the network once the interrupt lands. A tile whose request was already in flight when the interrupt landed still runs to completion and is recorded normally -- only work that had not started is cancelled. What the interrupt adds: the exception carries a partial_schedule attribute -- an AreaSchedule (a list of them for a multi-payload call), built from every tile that had an outcome before the interrupt landed. Pass it straight to run_area(..., retry_from=exc.partial_schedule) to resubmit only what is still missing; the tiles already accepted are carried forward, not re-billed. partial_schedule is only set when at least one tile had reached the submit pool; an interrupt during local planning (tiling, site preparation), before any request was sent, leaves nothing to resume and nothing is attached.

on_accepted(job_id, tile_key), when given, is called once for each job the submit loop records, at the moment it records it, so a caller can store accepted job ids before this method returns (a process that is killed then does not lose them). tile_key is the schedule key (tile_id or {tile_id}#batch{i}); for a multi-payload list the same key can occur once per payload. It is not called for a failed or uncertain submission, nor for jobs that retry_from carries forward. It is a read-only observer: it is called from the submit worker threads, one call at a time, and an exception from it is logged and ignored, so it never changes the schedule or what is billed.

Parameters:

Name Type Description Default
payload AnalysesUnion or list of AnalysesUnion

The analysis to run, or a list of analyses to run over the same polygon. Only area analyses are accepted (not the interior models), and sensor_points payloads are not supported. In a list, no two payloads may be identical.

required
polygon dict

A GeoJSON Polygon object that defines the area.

required
buildings AreaBuildings or mapping

Building meshes, as the AreaBuildings object returned by buildings.get_area or a mapping of building id to DotBimMesh or dict. See above.

None
vegetation mapping

Vegetation features, keyed by id. See above.

None
ground_materials mapping

Ground-material layers: a mapping of material name to a GeoJSON FeatureCollection, for example the layers of the area object that ground_materials returns. An unknown material name raises ValueError.

None
max_tiles_override int

Override the default maximum number of non-empty tiles.

None
max_workers int

Size of the submit pool; see above. Defaults to 8.

DEFAULT_SUBMIT_WORKERS
webhook_url str

URL to notify about the submitted jobs. On a retry, the value saved in retry_from is used when this is None.

None
webhook_events list of str

Webhook events to subscribe to. On a retry, the saved value is used when this is None.

None
retry_from AreaSchedule

A schedule from a previous call, to resubmit only its failed tiles. See above.

None
known dict

A job id to status map from a previous check_area_state call. Only read together with retry_from, to avoid extra status requests.

None
max_sensors_per_job int

Per-job sensor cap for facade (analysis_surfaces) payloads, in retained sensors: a whole number from 1 to 250 000. A fractional value is rounded down with a DeprecationWarning. Ignored, with a UserWarning, for payloads without analysis_surfaces. On a retry it must match the saved schedule.

None
terrain_context_margin_m float

How far, in metres, ground geometry is sliced beyond each tile. See above.

None
transport (json, binary)

Request representation. None (default) uses the saved schedule's transport on a retry, otherwise the client's transport, otherwise binary (JSON for analyses that have no binary route).

"json"
on_accepted callable

Called as on_accepted(job_id, tile_key) for each accepted job. See above.

None

Returns:

Type Description
AreaSchedule or list of AreaSchedule

The record of the submitted jobs, to pass to check_area_state, merge_area_jobs or retry_from. A list of schedules, one per payload and in the same order, when payload is a list. A tile whose submission failed is recorded on the schedule (failed_submissions, or uncertain_submissions when the outcome is unknown) rather than raised.

Raises:

Type Description
PolygonValidationError

If polygon is not a valid GeoJSON polygon or produces more tiles than the limit. It is a subclass of ValueError.

ValueError

If a payload is not an area analysis or is a sensor_points payload, if payloads in a list are identical, if transport, max_sensors_per_job or terrain_context_margin_m is invalid, if a layer is invalid, or if retry_from is used with a list of more than one payload or does not match this call (a different payload, weather, site inputs, terrain margin, sensor cap or transport, or a schedule written by an older SDK version that cannot be resumed).

KeyboardInterrupt

If the call is interrupted; see above for partial_schedule.

check_area_state

check_area_state(
    schedule: AreaSchedule, *, known: Optional[Dict[str, Any]] = None
) -> AreaState

Query job statuses and return the area state.

known is the answer from a PREVIOUS call: a job id -> status map, updated IN PLACE. A job whose recorded status is terminal (succeeded or failed) is not asked about again — pass the same dict across repeated calls to skip re-querying every finished job. Also read by a run_area(retry_from=...) call that passes its own known, so a retry plan built from a prior poll issues no extra status requests.

Parameters:

Name Type Description Default
schedule AreaSchedule

The schedule returned by run_area.

required
known dict

A job id to status map from a previous call, updated in place with the statuses found by this call.

None

Returns:

Type Description
AreaState

A snapshot of the job statuses: per-job states, counts per status, and whether the area is complete. A job whose status could not be read is reported as unknown and is asked about again on the next call.

merge_area_jobs

merge_area_jobs(
    schedule: AreaSchedule,
    max_workers: int = DEFAULT_MERGE_WORKERS,
    *,
    strategy: str = "default",
    wind_direction_deg: Optional[float] = None,
    _known_states: Optional[Dict[str, Any]] = None,
) -> Union[AreaResult, SurfaceAnalysisResult]

Download and merge results for succeeded jobs in a schedule.

Each tile is merged as soon as its download completes; the SDK keeps no copy of it. merged_grid keeps the dtype the server sent: float16, float32, int16 (UTCI/TCI, with value_divisor and valid) or, for a run that mixes dtypes, float64. Use AreaResult.physical_grid() for physical values.

Can be called at any time; it does not require every job to be finished.

Parameters:

Name Type Description Default
schedule AreaSchedule

The schedule returned by run_area.

required
max_workers int

Width of the DOWNLOAD pool, separate from the submit pool run_area uses. Defaults to DEFAULT_MERGE_WORKERS (8). The only measurement behind that number is that 1 is too slow; 4 and above measured the same.

DEFAULT_MERGE_WORKERS
strategy str

"default" — plain centre-crop merge. "directional_blend" — directional blend. Wind-speed only; requires wind_direction_deg or raises ValueError.

'default'
wind_direction_deg float

Meteorological wind-from direction in degrees (0=N, 90=E, 270=W).

None

Returns:

Type Description
AreaResult or SurfaceAnalysisResult

An AreaResult with the merged grid for a grid analysis, or a SurfaceAnalysisResult for a facade (analysis_surfaces) or custom-sensor run.

Raises:

Type Description
AreaRunError

If every job failed, or if any tile did not contribute a result, so that a grid with holes is never returned silently.

ValueError

If strategy is not "default" for an analysis other than wind-speed, or if strategy is "directional_blend" without wind_direction_deg.

forget_schedule

forget_schedule(schedule: AreaSchedule) -> None

Release the captures this client kept for the jobs of schedule.

Call it for a schedule you will never merge. The client keeps the capture of every facade or roof job until the join consumes it, and a schedule you drop cannot tell the client it is done. Jobs over the same geometry share one stored copy, so this frees only the copy that no other job of this client still uses. After this call merge_area_jobs(schedule) still merges, but without the outline (columns.render_buffers() then raises ValueError).

Warnings

A schedule made with retry_from=other carries the same job ids as other for the jobs it did not resubmit. A job id holds one reference, so forgetting one of the two releases those jobs for both.

Parameters:

Name Type Description Default
schedule AreaSchedule

The schedule whose captures are released.

required

run_area_and_wait

run_area_and_wait(
    payload: AnalysesUnion,
    polygon: dict,
    *,
    buildings: Optional[
        Union[AreaBuildings, Mapping[str, Union[DotBimMesh, dict]]]
    ] = None,
    vegetation: Optional[Mapping[str, dict]] = None,
    ground_materials: Optional[Mapping[str, dict]] = None,
    job_timeout: int = 300,
    area_timeout: int = 3600,
    on_progress: Optional[Callable[[AreaState], None]] = None,
    max_sensors_per_job: Optional[int] = None,
    terrain_context_margin_m: Optional[float] = None,
    max_tiles_override: Optional[int] = None,
    max_workers: int = DEFAULT_SUBMIT_WORKERS,
    webhook_url: Optional[str] = None,
    webhook_events: Optional[List[str]] = None,
    transport: Optional[str] = None,
    on_accepted: Optional[Callable[[str, str], None]] = None,
    retries: int = 1,
) -> Union[AreaResult, SurfaceAnalysisResult]
run_area_and_wait(
    payload: List[AnalysesUnion],
    polygon: dict,
    *,
    buildings: Optional[
        Union[AreaBuildings, Mapping[str, Union[DotBimMesh, dict]]]
    ] = None,
    vegetation: Optional[Mapping[str, dict]] = None,
    ground_materials: Optional[Mapping[str, dict]] = None,
    job_timeout: int = 300,
    area_timeout: int = 3600,
    on_progress: Optional[Callable[[AreaState], None]] = None,
    max_sensors_per_job: Optional[int] = None,
    terrain_context_margin_m: Optional[float] = None,
    max_tiles_override: Optional[int] = None,
    max_workers: int = DEFAULT_SUBMIT_WORKERS,
    webhook_url: Optional[str] = None,
    webhook_events: Optional[List[str]] = None,
    transport: Optional[str] = None,
    on_accepted: Optional[Callable[[str, str], None]] = None,
    retries: int = 1,
) -> List[Union[AreaResult, SurfaceAnalysisResult]]
run_area_and_wait(
    payload: Union[AnalysesUnion, List[AnalysesUnion]],
    polygon: dict,
    *,
    buildings: Optional[
        Union[AreaBuildings, Mapping[str, Union[DotBimMesh, dict]]]
    ] = None,
    vegetation: Optional[Mapping[str, dict]] = None,
    ground_materials: Optional[Mapping[str, dict]] = None,
    job_timeout: int = 300,
    area_timeout: int = 3600,
    on_progress: Optional[Callable[[AreaState], None]] = None,
    max_sensors_per_job: Optional[int] = None,
    terrain_context_margin_m: Optional[float] = None,
    max_tiles_override: Optional[int] = None,
    max_workers: int = DEFAULT_SUBMIT_WORKERS,
    webhook_url: Optional[str] = None,
    webhook_events: Optional[List[str]] = None,
    transport: Optional[str] = None,
    on_accepted: Optional[Callable[[str, str], None]] = None,
    retries: int = 1,
) -> Union[
    AreaResult,
    SurfaceAnalysisResult,
    List[Union[AreaResult, SurfaceAnalysisResult]],
]

Submit area jobs, poll until complete, merge and return results.

max_workers reaches the SUBMIT pool only. The merge download pool keeps its own width: it always runs at DEFAULT_MERGE_WORKERS (8) and is not reachable from here. A caller who needs to size the merge pool runs the three steps themselves — run_area, check_area_state and your own polling loop, then merge_area_jobs(schedule, max_workers=...) — which is the same sequence this method performs. This method starts bounded download, decode and per-tile folding when polling confirms a successful job. It returns results only after the final states and merge checks pass.

retries (default 1; 0 turns it off): resubmit failed, compute-failed and uncertain keys with the terminal states already learned, up to retries rounds. An incomplete merge raises only after the last round. No retry after a 402 abort.

Parameters:

Name Type Description Default
payload AnalysesUnion or list of AnalysesUnion

The analysis to run, or a list of analyses to run over the same polygon. See run_area.

required
polygon dict

A GeoJSON Polygon object that defines the area.

required
buildings mapping

Input layers, as for run_area.

None
vegetation mapping

Input layers, as for run_area.

None
ground_materials mapping

Input layers, as for run_area.

None
job_timeout int

Accepted for compatibility; it has no effect on this method.

300
area_timeout int

Seconds to wait for all jobs to finish in each round. Defaults to 3600.

3600
on_progress callable

Called with an AreaState after each status check.

None
max_sensors_per_job int

Per-job sensor cap for facade payloads; see run_area.

None
terrain_context_margin_m float

Terrain reach in metres; see run_area.

None
max_tiles_override int

Override the default maximum number of non-empty tiles.

None
max_workers int

Size of the submit pool; see above. Defaults to 8.

DEFAULT_SUBMIT_WORKERS
webhook_url str

URL to notify about the submitted jobs.

None
webhook_events list of str

Webhook events to subscribe to.

None
transport (json, binary)

Request representation; see run_area. Defaults to the client's transport.

"json"
on_accepted callable

Called as on_accepted(job_id, tile_key) for each accepted job; see run_area.

None
retries int

Number of retry rounds; see above. Defaults to 1.

1

Returns:

Type Description
AreaResult or SurfaceAnalysisResult or list

The merged result: an AreaResult for a grid analysis, or a SurfaceAnalysisResult for a facade or custom-sensor run. A list with one result per payload, in the same order, when payload is a list.

Raises:

Type Description
AreaTimeoutError

If area_timeout is reached before all jobs finish.

AreaRunError

If, after the last retry round, every job failed or any tile did not contribute a result.

ValueError

For the invalid inputs described under run_area.