> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/aerial/aodt/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/aerial/aodt/_mcp/server.

# Scene Generation

This page covers the concepts behind the pipeline that powers `DigitalTwinClient.prepare_map()`. For a practical quickstart, see [GIS Pipeline](/scene-building).

## Overview

Scene generation converts geospatial data into AODT-ready maps.

AODT maps support data layers such as buildings, building interiors, terrain, and vegetation. These data may be created procedurally, by importing GIS source data, or some combination of the two.

## Available parameters

AODT supports two building import methods: [OpenStreetMap](https://www.openstreetmap.org/) (OSM) and [CityGML](https://www.ogc.org/standard/citygml/) (GML). Additional parameters let you generate further layers (terrain, vegetation, interiors) or modify the scene.

Below are the parameters which may be passed as keyword arguments when constructing `OSMTask` or `GMLTask`. Default `None` defers to the GIS pipeline defaults.

| Parameter                     | Description                                                                                                                                                                                                                                                                                                                   | Required | Applies to |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- |
| `output_folder_key`           | S3 key prefix under the bucket. The pipeline writes `/sim` and `/viz` subfolders here.                                                                                                                                                                                                                                        | Yes      | Both       |
| `coords`                      | Bounding box as `(min_lon, min_lat, max_lon, max_lat)` in degrees. Supported to 25 km².                                                                                                                                                                                                                                       | Yes      | OSM        |
| `input_files`                 | Server-accessible CityGML file paths (must be reachable from the GIS worker container).                                                                                                                                                                                                                                       | Yes      | GML        |
| `epsg_in`                     | Input coordinate reference system as an EPSG code, e.g. `"6697"`. May be geographic (angular units, e.g. lat/lon) or projected (linear units, e.g. meters).                                                                                                                                                                   | Yes      | GML        |
| `include_elevation`           | Fetch and use terrain elevation. When `False`, the pipeline emits a synthetic, flat ground plane.                                                                                                                                                                                                                             | Yes      | Both       |
| `epsg_out`                    | Output CRS as an EPSG code. Must be a projected (linear-units) CRS. When omitted, the pipeline auto-derives the Universal Transverse Mercator (UTM) zone for the scene center. Set this explicitly when you need a specific projection — for example, to align with other geospatial data you plan to combine with the scene. | No       | GML        |
| `ground_source`               | Terrain source: `"terrarium"` (default) or `"srtm"`. For GML jobs, the input CityGML's `TINRelief` is used as terrain when present, with Terrarium as a fallback.                                                                                                                                                             | No       | Both       |
| `vegetation_source`           | Vegetation source method. Only `"procedural"` is currently supported via the client; other values produce no vegetation. Default: `"procedural"`.                                                                                                                                                                             | No       | Both       |
| `vegetation_density`          | Target trees per hectare for procedural vegetation. Default: `50`.                                                                                                                                                                                                                                                            | No       | Both       |
| `vegetation_scale_min`        | Minimum random scale applied to procedural trees. Default: `0.8`.                                                                                                                                                                                                                                                             | No       | Both       |
| `vegetation_scale_max`        | Maximum random scale applied to procedural trees. Default: `1.2`.                                                                                                                                                                                                                                                             | No       | Both       |
| `cesium3dtiles_b3dm`          | Emit Batched 3D Model (B3DM) tiles instead of binary glTF (GLB). Defers to GIS pipeline (currently B3DM). Default: `None`.                                                                                                                                                                                                    | No       | Both       |
| `cesium3dtiles_draco`         | Apply Draco mesh compression to GLB tiles. Requires `cesium3dtiles_b3dm=False`; auto-enabled when b3dm is off. Defers to GIS pipeline (currently off when b3dm is on). Default: `None`.                                                                                                                                       | No       | Both       |
| `cesium3dtiles_gzip`          | Gzip-compress tile payloads. Hosting layer must set `Content-Encoding: gzip`. Defers to GIS pipeline (currently on). Default: `None`.                                                                                                                                                                                         | No       | Both       |
| `cesium3dtiles_chunk_size`    | Spatial partition size in meters for tile generation. Omit/`None` to disable chunking.                                                                                                                                                                                                                                        | No       | Both       |
| `cesium3dtiles_veg_instanced` | Use GPU instancing for vegetation tiles (smaller, faster). Default: `True`.                                                                                                                                                                                                                                                   | No       | Both       |
| `disable_interiors`           | Skip per-building floor-slice generation. Default: `False`.                                                                                                                                                                                                                                                                   | No       | Both       |
| `terrain_clip_margin`         | Terrain clip radius in meters beyond the building extent. Default: `200`.                                                                                                                                                                                                                                                     | No       | Both       |
| `rough`                       | Use approximate cuts when generating the mobility-domain mesh. Default: `True`.                                                                                                                                                                                                                                               | No       | Both       |
| `terraform_config`            | Optional [`TerraformConfig`](#terraformconfig-fields) overrides for the terrain-shaping pass.                                                                                                                                                                                                                                 | No       | Both       |

### `TerraformConfig` fields

`TerraformConfig` controls the terrain-shaping pass that fits terrain to buildings. It is only available when using terrain (rather than synthetic, flat ground). Every field is optional; `None` falls back to the GIS pipeline default shown below.

| Parameter                     | Description                                                                                        | Default |
| ----------------------------- | -------------------------------------------------------------------------------------------------- | ------- |
| `terraform`                   | Conform terrain to building base heights.                                                          | `False` |
| `pad_radius`                  | Building footprint padding radius (meters).                                                        | `8.0`   |
| `pre_tessellation_length`     | Target edge length for pre-terraform tessellation (meters).                                        | `4.0`   |
| `pre_smooth_terrain`          | Smooth terrain before terraforming.                                                                | `True`  |
| `pre_smooth_iters`            | Pre-smooth iterations.                                                                             | `2`     |
| `pre_smooth_lambda`           | Pre-smooth Laplacian lambda.                                                                       | `0.5`   |
| `terraform_smooth`            | Smooth terrain after terraforming.                                                                 | `True`  |
| `terraform_smooth_iters`      | Post-terraform smoothing iterations.                                                               | `8`     |
| `terraform_smooth_lambda`     | Post-terraform Laplacian lambda.                                                                   | `0.6`   |
| `terraform_smooth_radius`     | Smoothing radius in meters (`0` = global).                                                         | `0.0`   |
| `building_base_method`        | How to derive each building's base height: `"min"`, `"max"`, `"average"`, `"top10"`, `"bottom10"`. | `"max"` |
| `base_merge_distance`         | Distance threshold (m) for merging nearby building bases.                                          | `175.0` |
| `base_influence_radius`       | Radius of influence (m) for base-height blending.                                                  | `140.0` |
| `base_influence_sigma`        | Gaussian sigma (m) for base-height blending.                                                       | `80.0`  |
| `base_smooth_iters`           | Base-height smoothing iterations.                                                                  | `1`     |
| `adaptive_bands`              | Use adaptive near/far tessellation bands.                                                          | `False` |
| `near_radius`                 | Near-band radius in meters.                                                                        | `1.5`   |
| `near_tessellation_threshold` | Edge-length threshold (m) for near-band tessellation.                                              | `5.0`   |
| `far_tessellation_threshold`  | Edge-length threshold (m) for far-band tessellation.                                               | `100.0` |

### Example

A typical OSM job with a partial `TerraformConfig` override. Any field left as `None` falls back to the GIS pipeline default:

```python
from dt_client import DigitalTwinClient, OSMTask, TerraformConfig
from _config import S3Config

client = DigitalTwinClient("localhost:50051")

task = OSMTask(
    output_folder_key="demo_gis/tokyo",
    coords=(139.74, 35.66, 139.75, 35.67),
    include_elevation=True,
    terraform_config=TerraformConfig(
        terraform=True,
        base_influence_sigma=75.0,
        terraform_smooth_iters=7,
    ),
)

s3 = S3Config(
    bucket="aerial-data",
    provider="minio",
    endpoint_url="http://<worker-host>:9002",
    access_key="minioadmin",
    secret_key="minioadmin",
)

result = client.prepare_map(task, s3)
print(result["s3_url"])
```

See `client/examples/example_prepare_map_terraform.py` for a full CLI version.

## Output layout

A successful job writes the following layout. AODT scenes are stored as Universal Scene Description (USD) files alongside JSON metadata sidecars:

```
s3://aerial-data/demo_gis/tokyo/
  sim/
    scene_metadata.json     # sidecar describing the scene's georeferencing and provenance
    master.usd              # tiled-USD master stage (paired with master_metadata.json)
    vegetation.geojson      # vegetation features (when generated)
  viz/
    tiles/                  # Cesium 3D Tiles for the viewer (exterior, interior, vegetation)
    terrain/                # quantized-mesh terrain tiles
```

Inside `master.usd`, the key prims are:

* **Buildings** — meshes with material assignments and per-face `GlobalSurfaceHash` primvars (USD per-primitive variables). The hash is a stable per-face identifier that lets calibration and result lookups join back to specific building surfaces across runs.
* **Terrain / ground plane** — the terrain mesh, conformed to building bases when terraform is enabled. With no elevation source, this is a flat plane.
* **Interiors** — floor-slice meshes generated per building (skipped when `disable_interiors=True`). Buildings with ambiguous geometry are skipped here and are also excluded from indoor user equipment (UE) mobility, so the two stay consistent.
* **Vegetation** — tree instances. See [Vegetation](#vegetation) for how candidates are sampled and filtered if placed procedurally.
* **Georeferencing metadata** — recorded as USD attributes (`asim:crs`, `asim:center_lat`, `asim:center_lon`, `asim:vertical_datum`) on the stage. To inspect without opening the USD, read the mirrored fields from `master_metadata.json`. Calibration, AODT simulations, and the viewer all read these to anchor the scene to real-world coordinates.

Each top-level USD is accompanied by a sidecar `*_metadata.json` file. It is a small JSON document recording:

* **Georeferencing** — `crs`, `center_lat`, `center_lon`, `center_z`, `vertical_datum`, and the projected anchor (`anchor_x`, `anchor_y`). These mirror the `asim:*` attributes on the USD and let downstream tools read the scene's georeferencing without opening the USD.
* **Provenance** — `date_created`, `input_hash` and `input_hash_recipe` (a stable fingerprint of the job parameters and source-file bytes), and `code_version` (a map of the library versions — aodt, pyproj, proj, gdal, rasterio, usd, python — that influenced the output). The same fields are also written into the USD's `customLayerData` under `gis:*` keys.

## Terrain

The pipeline imports terrain when `include_elevation=True`. When `include_elevation=False`, the pipeline emits a synthetic, flat ground plane — `TerraformConfig` settings are then ignored, since there is no terrain to reshape.

### Terrain sources

The `ground_source` field selects which digital elevation model (DEM) provider feeds the terrain mesh:

| Source        | Notes                                                                             |
| ------------- | --------------------------------------------------------------------------------- |
| `"terrarium"` | [AWS Terrarium DEM tiles](https://registry.opendata.aws/terrain-tiles/). Default. |
| `"srtm"`      | [NASA SRTM tiles](https://www.earthdata.nasa.gov/data/instruments/srtm).          |

For GML jobs, if the input CityGML files contain `TINRelief` (Triangulated Irregular Network) features, those features are imported as terrain automatically — you do not need to set `ground_source`.

### Conforming buildings to terrain

Imported buildings and terrain rarely line up by default — the two data sources often have different vertical references, and even when they share one, slopes and DEM noise can leave buildings clipping into the ground or floating above it. The pipeline reconciles them in one of two ways.

**Default behavior — building grounding.** Every map generation includes a step that matches buildings to terrain. For each building, the pipeline samples the terrain elevation under the footprint center and rigid-translates the building in Z so its base sits at that height. No geometry is reshaped; buildings are dropped onto the terrain. This handles the common case of mismatched datums and gently varying terrain.

How well this simple grounding works depends on terrain resolution and shape. On flat or smoothly varying terrain it is usually enough. On noisy DEMs, steep slopes, or coarse-resolution terrain under fine-grained footprints, individual buildings can still clip into or float above the ground.

**For greater control — `terraform_config.terraform=True`.** For these cases, the pipeline can additionally reshape the terrain itself so each building sits cleanly:

1. For each footprint, derive a representative base elevation from the terrain samples underneath it (`building_base_method`, default `"max"`).
2. Smooth those base elevations across neighboring buildings so adjacent footprints share consistent heights (`base_merge_distance`, `base_influence_radius`, `base_influence_sigma`, `base_smooth_iters`).
3. Re-tessellate and reshape the terrain mesh locally so it is flat at the smoothed base elevation under each footprint, with a `pad_radius` blend ring outside.
4. Optionally smooth the terraformed terrain (`terraform_smooth`, `terraform_smooth_iters`, `terraform_smooth_lambda`).
5. Re-seat buildings on the now-flattened patches.

See [`TerraformConfig` fields](#terraformconfig-fields) for the full set of tuning knobs.

## Vegetation

The pipeline places trees procedurally across the scene's plantable area. The pipeline writes `output_folder_key/sim/vegetation.geojson`, describing every placed tree for downstream consumers.

### Placement algorithm

Placement runs in three phases.

**1. Build the permissible area.** The scene bounding box minus a union of exclusion polygons derived from the input data. Excluded categories include buildings, roads, railways, water bodies (inland and supplemental marine sources), bare-rock terrain (rock, scree, sand, dunes, glaciers, beaches), harbors and piers, sports facilities, playgrounds, parking, runways and taxiways, and power infrastructure. For GML jobs, GML building footprints are added to the exclusion set on top of any OSM data.

**2. Place mapped trees first.** OSM `natural=tree` and `natural=tree_row` features are imported at their exact coordinates and count toward the density target. When an OSM tree carries a `height` tag, the tag is used to derive its scale; otherwise a uniform random scale is drawn.

**3. Procedural fill.** The remaining target count is sampled inside the permissible area using a [Thomas cluster process](https://en.wikipedia.org/wiki/Cluster_process): parent points are drawn uniformly across the permissible area, and each parent emits a small cluster of child points with Gaussian scatter. This produces natural-looking groves and clearings rather than a uniform grid. A minimum-spacing pass then thins out positions that came out too close to each other.

After placement, an **overlap-resolution pass** keeps canopies clean:

* For each pair of overlapping canopies, the larger of the two trees is shrunk so the canopies just touch.
* Trees adjacent to buildings are shrunk so their canopies do not enter the footprint.
* Trees that would have to shrink below `vegetation_scale_min` are removed.

### Vegetation model and sizing

Every tree is an instance of a single shared prototype with a canopy diameter of about 6 m and a total height of about 10 m at scale 1.0. Per-instance size is randomized:

* A uniform random scale is drawn from `[vegetation_scale_min, vegetation_scale_max]` (default `0.8`–`1.2`) and applied isotropically to the whole tree (canopy and trunk together).
* Mapped OSM trees with a `height` tag have their scale derived from the tag (`height / 10 m`), clipped to the same `[vegetation_scale_min, vegetation_scale_max]` range.

### Density

`vegetation_density` is a *target* in trees per hectare across the permissible area. The pipeline computes a target count `density × permissible_area_ha`, subtracts the number of mapped OSM trees, and asks the procedural sampler for the remainder.

The achieved count is usually lower than the target. Each of the steps above can drop trees: positions outside the permissible area after Gaussian scatter are filtered, the minimum-spacing pass thins clusters, and the overlap-resolution pass shrinks or removes trees that crowd buildings or each other. As a result:

* **Sparse scenes** typically meet the target density.
* **Dense urban scenes** saturate well below the target. Increasing `vegetation_density` past the saturation point yields diminishing returns; lower it if you want a measured outcome to match the configured value.
* **Fully excluded scenes** — for example a bounding box that lies entirely inside buildings or water — emit zero trees and log `No permissible area for vegetation`.

Setting `vegetation_density=0` is a special case: procedural fill is disabled entirely, and only mapped OSM trees are placed. Use this when you want to keep real-world tree locations without adding any synthetic ones.

## Indoor

The pipeline generates per-building interior geometry. Buildings shorter than one floor, or with self-intersecting or otherwise unusable geometry, are skipped.

To skip indoor generation entirely, set `disable_interiors=True`. This is the right choice when:

* You only need outdoor links.
* You're iterating on map geometry and want shorter pipeline runs.
* Your scene has no buildings (terrain-only export).

## Common issues

### `coords` area is too large

In practice, the pipeline supports map areas up to 25 km². A hard cap is enforced at 100 km², after which `client.prepare_map()` returns `success=False` with this message:

```
Map generation input error: 'coords' area is too large (>100 km²). Use a smaller bounding box.
```

Even within the technical cap, processing time grows quickly past 25 km². Use a smaller box, or split your region across multiple `prepare_map` calls.

### Missing or malformed S3 endpoint

When `S3Config(provider="minio", endpoint_url=...)` is wrong, the workflow fails with an upload error. Unlike the bounding-box case above, these errors raise from `client.prepare_map()` as a `RuntimeError` (or `ValueError` for malformed URLs) — they do not return `success=False`.

Common forms:

```
RuntimeError: S3 connection/credential error, aborting upload: Could not connect to the endpoint URL: "http://wrong-host:9002/..."
RuntimeError: S3 configuration error (NoSuchBucket), aborting upload: ...
ValueError: endpoint_url must be an HTTP(S) URL, got: <url>
```

Check that:

* `endpoint_url` starts with `http://` or `https://` and is reachable from the worker host.
* The bucket exists and the access/secret keys are correct.
* For the default worker stack, MinIO uses `minioadmin` / `minioadmin`.

### Cesium tiles render fails

Gzip-compressed tiles (the GIS pipeline's current default) require the hosting layer to set `Content-Encoding: gzip` on each object. If your host does not, either configure it to do so or pass `cesium3dtiles_gzip=False` to `prepare_map`.

## Reference

* [DigitalTwinClient.prepare\_map](/api/client#prepare_map)
* [OSMTask](/api/client#osmtask), [GMLTask](/api/client#gmltask), and `TerraformConfig`
* [GIS Pipeline quickstart](/scene-building)
* [Configuring Sim YAML](/configuring-sim-yaml)
* [Limitations](/limitations#gis)