build_AWSIM_map
收藏资源简介:
# build_AWSIM_map > 中文版本见 [README.zh-CN.md](README.zh-CN.md). / Chinese version: [README.zh-CN.md](README.zh-CN.md). Tooling that turns a real-world ROS 2 sensor recording into the artefacts a full **AWSIM + Autoware** simulation needs to load a track for self-driving testing. Two sets of outputs, both produced by this repo: **Autoware side** (consumed by `map_loader`, NDT, planner): - **`pointcloud_map.pcd`** — LiDAR-built point cloud of the environment, used by Autoware's NDT matching for localisation. - **`lanelet2_map.osm`** — vector HD map (centreline + lane boundaries) used by the planner and the simulator for routing and behaviour. - **`map_origin.yaml`** — the geodetic anchor (lat / lon / alt) for the local ENU frame the rest of the pipeline lives in. **AWSIM side** (consumed by Unity for physics + sensor simulation): - **`meshes/ground.obj`** — drivable surface for the Unity wheel raycast. - **`meshes/wall_left.obj`**, **`meshes/wall_right.obj`** — generated wall candidates from vertical PCD returns. Check `meshes_summary.txt` and inspect coverage before relying on them as continuous barriers. - **`meshes/meshes_manifest.yaml`** — file → Unity layer + RGL material category mapping. The pipeline runs in three stages — pure Python, no ROS or PCL required: ``` data/<track>/raw.db3 (input: ROS 2 SQLite-3 bag) │ │ db3_to_mcap_converter/convert_db3_to_mcap.py │ (one pass: embeds schemas, bakes Whale Dynamic's │ sensor_tf_no_camera.yaml into /tf_static, and │ renames the secondary lidar's frame_id) ▼ data/<track>/cleaned_tf.mcap (Foxglove-loadable, TF-aware) │ │ mcap_to_pcd/build_map.py │ (pose pass + dual-lidar voxel accumulation + │ Lanelet2 OSM seed; auto Z-gate from trajectory) ▼ map/<track>/{pointcloud_map.pcd, (Autoware-side: NDT input) lanelet2_map.osm, (Autoware-side: lane HD map) map_origin.yaml, (geodetic anchor) trajectory_enu.csv} (used as road centreline below) │ │ pcd_to_meshes/build_meshes.py │ (corridor filter + PCA verticality + ground heightmap + │ per-side (arclength, height) wall mesh) ▼ map/<track>/meshes/{ground.obj, (AWSIM-side: Unity colliders) wall_left.obj, wall_right.obj, meshes_manifest.yaml} (Unity layer + RGL material) ``` ## Quick start If you only want to *use* the pre-built map, you can stop after `git lfs pull` — `map/TM99_uphill/` and `map/TM99_high_res/` already contain the generated Autoware artefacts plus Unity OBJ mesh outputs. To **regenerate** the map from the source bag (or when working with a new recording): > **Note.** Neither `raw.db3` nor `cleaned_tf.mcap` is shipped — only the > compressed archive (`raw.tar.zst` for uphill, `raw.tar.xz` for downhill) > is. Both are derived artefacts you reconstitute locally. Plan ~96 GB of > free disk for the extracted bag plus another ~96 GB for the MCAP step. > Both files are gitignored so a regeneration will never accidentally > end up in a commit. ```bash # 0. one-off: extract the source bag from its compressed archive. # bsdtar / GNU tar auto-detect both zst and xz from the magic bytes. cd data/TM99_uphill tar -xf raw.tar.zst # ~5–15 min, produces raw.db3 (~96 GB) cd ../.. # 1. one-off: convert the source bag → cleaned_tf.mcap (schemas + /tf_static in one pass) cd db3_to_mcap_converter pip install --user -r requirements.txt python3 convert_db3_to_mcap.py \ --src ../data/TM99_uphill/raw.db3 \ --dst ../data/TM99_uphill/cleaned_tf.mcap # ~60–90 min, ~96 GB output # 2. build the Autoware-side map (PCD + Lanelet2 + origin + trajectory) cd ../mcap_to_pcd pip install --user -r requirements.txt python3 build_map.py \ --mcap ../data/TM99_uphill/cleaned_tf.mcap \ --out-dir ../map/TM99_uphill \ --voxel-size 0.2 \ --lidar-decimate 5 # ~5–10 min # 3. build the AWSIM-side Unity meshes (ground + walls + manifest) cd ../pcd_to_meshes pip install --user -r requirements.txt python3 build_meshes.py \ --pcd ../map/TM99_uphill/pointcloud_map.pcd \ --trajectory ../map/TM99_uphill/trajectory_enu.csv \ --out-dir ../map/TM99_uphill/meshes # ~2–5 min ``` After step 2, `map/TM99_uphill/pointcloud_map.pcd` is in local ENU and `map/TM99_uphill/map_origin.yaml` carries the geodetic anchor — feed both to Autoware's `map_loader` and the simulator knows where every point sits in WGS84. `lanelet2_map.osm` is already in geodetic lat/lon natively. After step 3, `map/TM99_uphill/meshes/` contains Unity-importable geometry: drop the `.obj` files into your AWSIM scene and assign layers per `meshes_manifest.yaml` (see "Launching in AWSIM" below). The Unity ENU origin matches `map_origin.yaml`, so the meshes register exactly against the PCD without any offset. Before driving, inspect `meshes_summary.txt` and the OBJs themselves; sparse wall triangle counts mean the generated walls are not yet suitable as continuous side barriers. ### Rebuilding the TM99_high_res meshes from third-party SLAM outputs `map/TM99_high_res/` ships a third-party SLAM dataset (`slam_map.pcd`, `1780372428678.osm`, `offset.txt`) plus its prebuilt meshes. To re-derive the meshes (e.g. after tuning corridor or wall parameters), use the OSM- trajectory variant of the mesh builder — there's no MCAP bag for this dataset, so the road centreline is derived from the Lanelet2 lane boundaries: ```bash cd pcd_to_meshes pip install --user -r requirements.txt python3 build_meshes_from_osm.py \ --pcd ../map/TM99_high_res/slam_map.pcd \ --osm ../map/TM99_high_res/1780372428678.osm \ --offset-txt ../map/TM99_high_res/offset.txt \ --out-dir ../map/TM99_high_res/meshes # ~5–15 min ``` See [`pcd_to_meshes/README.md`](pcd_to_meshes/README.md) for which orchestrator to use with which dataset and the full flag reference. ## Launching in AWSIM [AWSIM](https://github.com/tier4/AWSIM) is the open-source Unity-based driving simulator from TIER IV. AWSIM provides the simulated vehicle, sensors, and Unity scene geometry; **Autoware** (running alongside AWSIM over ROS 2) consumes the map files for localisation, planning, and visualisation. A working track for AWSIM + Autoware co-simulation needs **both** sides: | side | files | consumed by | role | |---|---|---|---| | Autoware | `pointcloud_map.pcd`, `lanelet2_map.osm`, `map_origin.yaml` | `map_loader`, NDT, planner | localisation + routing | | Unity (AWSIM) | `meshes/ground.obj`, `meshes/wall_left.obj`, `meshes/wall_right.obj` | Unity physics + RGL lidar | wheels roll on ground; wall meshes can contain the car and provide lidar surfaces when their generated triangle coverage is dense enough | This repo produces both. The Unity meshes are aligned to the same ENU origin as the PCD, so once both sides are loaded with the same `map_origin.yaml`, the geometry registers exactly. ### 1. Lay the Autoware-side files out the way `map_loader` expects Pick any directory and drop the three artefacts plus a `map_projector_info.yaml` next to them: ``` my_track_map/ ├── pointcloud_map.pcd ← copy from map/TM99_uphill/ ├── lanelet2_map.osm ← copy from map/TM99_uphill/ ├── map_projector_info.yaml ← create — see below └── (trajectory_enu.csv) ← optional, for analysis only ``` Autoware reads `map_projector_info.yaml` to know how local-Cartesian PCD coordinates relate to global lat/lon. Generate it from the values in `map_origin.yaml`: ```yaml # map_projector_info.yaml — tells Autoware the map is in a local-tangent # ENU frame anchored at this geodetic point. projector_type: local vertical_datum: WGS84 map_origin: latitude: 29.0697514213 # copy from map_origin.yaml longitude: 110.4705323434 altitude: 327.9443 ``` For Autoware-style MGRS projection instead of local ENU, set `projector_type: MGRS` and replace `map_origin` with `mgrs_grid:` plus the relevant grid string — AWSIM's pre-shipped tutorial maps use this scheme. ### 2. Drop the Unity meshes into the AWSIM scene `map/<track>/meshes/` contains three OBJs that play three distinct roles in the AWSIM/Unity physics simulation. Setting them up correctly is the difference between a virtual ego car that drives on the track and one that falls through it or off the side. | OBJ | Role at runtime | Unity layer | Tag | Collider | RGL material | |---|---|---|---|---|---| | `ground.obj` | **Drivable surface** — the vehicle's wheel raycasts land on it. Defines where the wheels can roll. Does *not* prevent the car from leaving the track sideways. | `Ground` | `Ground` | MeshCollider, Convex = off | `asphalt` | | `wall_left.obj` | **Inboard side barrier** — the cliff face / mountain side. Stops the car from cutting the inside of switchbacks into solid rock. | `Wall` | `Wall` | MeshCollider, Convex = off | `rock` | | `wall_right.obj` | **Outboard side barrier** — the cliff drop / parapet / guardrail side. **This is what keeps the car from falling off the cliff** when the driver oversteers a switchback. | `Wall` | `Wall` | MeshCollider, Convex = off | `metal` | Same information lives in `meshes_manifest.yaml` for programmatic readers. #### Step 2.1 — Create the Unity layers and tags (one-time per Unity project) AWSIM's stock tutorial scenes ship with a `Ground` layer but **not** a `Wall` layer. Create the wall layer before importing the meshes, otherwise the wall prefabs land on the default layer and won't stop the car. 1. `Edit → Project Settings → Tags and Layers`. 2. Under **Tags**, add `Wall` (if not already present; `Ground` usually is). 3. Under **Layers**, find the first empty **User Layer** slot (User Layer 6, 7, …) and name it `Wall`. The numeric index doesn't matter, but remember it — you'll need it for the vehicle's collision mask check in step 2.4. #### Step 2.2 — Import all three OBJs 1. Drag `ground.obj`, `wall_left.obj`, `wall_right.obj` from `map/<track>/meshes/` into your Unity project's `Assets/` (a folder like `Assets/MyTrack/Meshes/` keeps things tidy). Unity automatically generates a mesh asset and a prefab for each. 2. In the Hierarchy, create an empty GameObject called `TrackEnv` at world origin `(0, 0, 0)`. **Do not transform it.** The meshes are already in ENU metres relative to `map_origin.yaml`, so any non-identity transform on `TrackEnv` would desync them from the PCD that Autoware uses for NDT localisation. 3. Drag each OBJ prefab into `TrackEnv` so the three meshes share the same parent. #### Step 2.3 — Set Layer, Tag, and Collider on each mesh For each of the three meshes (do this once per mesh, in the Inspector): 1. With the GameObject selected, set its **Layer** dropdown to the row shown in the table above (`Ground` for `ground.obj`, `Wall` for the two wall meshes). 2. Set its **Tag** to the same value (Unity's Tag dropdown). Tags are how AWSIM's RGL lidar can identify what surface a ray hit. 3. Add a **MeshCollider** component (`Add Component → Physics → MeshCollider`). Important settings: - **Convex**: **OFF**. These are static environment meshes; convex hulls would discard most of the geometry. - **Mesh**: should auto-populate to the OBJ's mesh asset. - **Cooking Options**: leave at default. - **Material**: optional; assign a Physic Material if you want custom friction (default = high friction, which matches asphalt). For visual rendering, the OBJs already have a Mesh Filter + Mesh Renderer from Unity's import. Assign a basic material in the renderer if you want them to look like something other than the default magenta. (The meshes don't ship with UVs/textures by design — they're colliders + RGL hit surfaces, not visual hero assets.) #### Step 2.4 — Confirm the vehicle is set up to collide with both layers AWSIM's stock vehicle prefab (e.g. the Lexus RX450h) collides with everything by default, so this normally requires no change. Two things to verify if you've customised the vehicle: 1. The vehicle's **wheel raycast** uses a Layer Mask that includes `Ground`. (Without this, the wheels can't roll on `ground.obj` and the car falls through.) 2. The vehicle Rigidbody's **collision matrix** allows its layer to collide with `Wall`. Check `Edit → Project Settings → Physics → Layer Collision Matrix` — the row/column intersection of the vehicle's layer and `Wall` should be checked. #### Step 2.5 — (Optional) Assign RGL materials for realistic lidar returns If your simulation uses AWSIM's RGL lidar plugin and you want intensity returns that match the real-bag intensity Autoware was tuned against: 1. Open the RGL **Mesh Material Properties** asset in your project. 2. For each of the three meshes, assign the RGL material from the table (`asphalt` for ground, `rock` for left wall, `metal` for right wall). This step is optional — the simulation runs without it; intensity just won't match real-world TM99 returns. #### Step 2.6 — Sanity-check that it works Press Play and drive the ego vehicle toward each kind of boundary: - **Drive onto the road** → wheels roll on `ground.obj`. If the car falls through, the ground mesh isn't on the `Ground` layer or the MeshCollider isn't attached. - **Steer hard into the inboard side of a switchback** → car should bounce off `wall_left.obj` and stay on the road. If it passes through, the wall isn't on the `Wall` layer or its MeshCollider has Convex = ON. - **Steer hard toward the outboard side of a switchback** → car should bounce off `wall_right.obj` instead of falling off the cliff. Same failure modes as above. ### 3. Get AWSIM and Autoware - **AWSIM** — clone from <https://github.com/tier4/AWSIM> and follow the setup guide at <https://tier4.github.io/AWSIM/>. For a custom track (anything other than the tutorial scene) you'll need to open the project in Unity to add your `TrackEnv` and rebuild a binary; the pre-built binary only ships with TIER IV's reference scene. - **Autoware** — follow the [Universe install guide](https://autowarefoundation.github.io/autoware-documentation/main/installation/), prebuilt Docker images cover most setups. ### 4. Launch the simulator Run AWSIM and Autoware in two terminals: ```bash # Terminal 1 — AWSIM (Unity) ./AWSIM.x86_64 # or `AWSIM.app` on macOS ``` ```bash # Terminal 2 — Autoware, pointed at this map ros2 launch autoware_launch e2e_simulator.launch.xml \ map_path:=$(pwd)/my_track_map \ vehicle_model:=sample_vehicle \ sensor_model:=awsim_sensor_kit ``` `e2e_simulator.launch.xml` brings up `map_loader`, `pointcloud_map_loader`, `lanelet2_map_loader`, NDT localisation, mission/behaviour planning, and the Rviz visualisation. AWSIM publishes simulated `/sensing/lidar/...` and `/sensing/imu` topics — RGL casts rays against your `ground.obj` / `wall_*.obj` meshes to generate them; Autoware locks onto the PCD with NDT, looks up the ego pose against the Lanelet2, and feeds back planning/control to the simulator. You can drive a route by setting a goal in Rviz the usual way. ### 5. Quick visualisation without Autoware If you just want to look at the map (no planner, no localiser), drag `pointcloud_map.pcd` and `lanelet2_map.osm` into [Foxglove](https://foxglove.dev/) or open the OSM in JOSM (with the `lanelet2-plugin`). The OSM carries geodetic coordinates natively; the PCD and OBJs are local ENU metres and use `map_origin.yaml` as their global anchor. The `.obj` meshes open in any viewer (MeshLab, Blender, three.js viewers, even Preview on macOS). ## Repository layout All binary files in `data/` and `map/` are LFS-tracked and ship with the ModelScope clone — no separate downloaders. See [`.gitattributes`](./.gitattributes) for the exact patterns. ``` build_AWSIM_map/ ├── data/ Raw sensor recordings (LFS-tracked via ModelScope) │ ├── TM99_uphill/ │ │ └── raw.tar.zst ROS 2 SQLite-3 bag, Zstandard-compressed (~45 GB, LFS) │ │ (extract with `tar -xf raw.tar.zst` to get raw.db3; then │ │ cleaned_tf.mcap is regenerated by │ │ db3_to_mcap_converter/convert_db3_to_mcap.py) │ └── TM99_downhill/ │ └── raw.tar.xz ROS 2 SQLite-3 bag, xz-compressed (~29 GB, LFS) │ (extract with `tar -xf raw.tar.xz`) ├── db3_to_mcap_converter/ .db3 → .mcap converter (one pass: embeds │ canonical ROS 2 schemas, bakes Whale │ Dynamic's sensor_tf_no_camera.yaml into │ /tf_static, and renames the secondary │ lidar's frame_id) ├── mcap_to_pcd/ cleaned_tf.mcap → Autoware-side map │ (pose extraction, dual-lidar voxel │ accumulation with ego-bbox vehicle-self │ filter, Lanelet2 OSM seed) ├── pcd_to_meshes/ Two orchestrators for the same library code: │ ├── build_meshes.py pointcloud_map.pcd + trajectory_enu.csv → │ │ AWSIM-side Unity meshes (TM99_uphill flow) │ └── build_meshes_from_osm.py slam_map.pcd + Lanelet2 OSM + offset.txt → │ AWSIM-side Unity meshes (TM99_high_res flow, │ OSM lane centrelines used as the trajectory) └── map/ Pre-built map outputs (tracked in repo via LFS) ├── TM99_uphill/ ← built by mcap_to_pcd + pcd_to_meshes │ ├── pointcloud_map.pcd binary PCD, x/y/z/intensity (~1.9 GB, LFS) │ ├── lanelet2_map.osm seed Lanelet2 (geodetic lat/lon) │ ├── map_origin.yaml ENU geodetic anchor │ ├── trajectory_enu.csv decimated ego trajectory in ENU │ └── meshes/ │ ├── ground.obj drivable surface (Unity layer: Ground) │ ├── wall_left.obj inner cliff face (Unity layer: Wall) │ ├── wall_right.obj outer parapet (Unity layer: Wall) │ └── meshes_manifest.yaml mesh → layer + RGL material map └── TM99_high_res/ ← third-party SLAM dataset + its meshes ├── slam_map.pcd ASCII PCD with normals + curvature (~2.3 GB, LFS) ├── 1780372428678.osm Lanelet2 HD map (480 lanelets) ├── offset.txt UTM 49R + LLA origin anchor └── meshes/ ground.obj + wall_left.obj + wall_right.obj + meshes_manifest.yaml + map_origin.yaml + trajectory_enu.csv (OSM-derived) ``` Each subdirectory has its own README with the run commands, expected runtime, and tunables. ## Cloning the dataset (ModelScope, Git LFS) This repository — code, prebuilt maps, AWSIM meshes, **and** the raw source recordings — is distributed as a single Git-LFS-backed dataset on ModelScope. The whole thing comes down in one `git clone`; you don't need any separate downloaders. Files routed through LFS (see [`.gitattributes`](./.gitattributes)): | pattern | what it covers | typical size | |---|---|---| | `*.tar.zst` | Zstandard-compressed source bag (`data/TM99_uphill/raw.tar.zst`) | ~45 GB | | `*.tar.xz` | xz-compressed source bag (`data/TM99_downhill/raw.tar.xz`) | ~29 GB | | `*.pcd` | point clouds (`map/<track>/pointcloud_map.pcd`, `map/TM99_high_res/slam_map.pcd`) | 1.8–2.3 GB | | `*.obj` | AWSIM Unity meshes under `map/<track>/meshes/` | up to ~140 MB | ```bash # One-time per machine git lfs install # Full clone (code + ~220 GB of LFS blobs) — this is slow, plan for a wired connection git clone https://www.modelscope.cn/datasets/<owner>/build_AWSIM_map.git # Code-only / sparse clone — skip LFS, then selectively pull what you need GIT_LFS_SKIP_SMUDGE=1 \ git clone https://www.modelscope.cn/datasets/<owner>/build_AWSIM_map.git cd build_AWSIM_map git lfs pull --include="map/**" # only prebuilt maps + meshes (~5 GB) git lfs pull --include="data/TM99_uphill/raw.tar.zst" # uphill source bag (~45 GB compressed) git lfs pull # everything (code + ~80 GB of LFS) ``` Without LFS, every binary file comes down as a tiny text pointer — Autoware will reject the PCD, Unity will refuse to import the meshes, and the converter scripts will choke on the empty bag. ### Pushing back to ModelScope (the upload that produced this dataset) For maintainers: assuming you've already created the dataset repo on [modelscope.cn](https://modelscope.cn) and have an access token configured locally, push the entire working tree in one shot: ```bash git lfs install # Add ModelScope as the remote (do this once) git remote add modelscope https://www.modelscope.cn/datasets/<owner>/build_AWSIM_map.git # Or, if you cloned from there originally, the remote 'origin' already points at it. git add . # stages code AND LFS-tracked binaries per .gitattributes git commit -m "Initial dataset upload" git push modelscope main # uploads code via git, large files via LFS ``` ModelScope LFS handles multi-GB files transparently. Expect the first push to take many hours for the full ~220 GB; subsequent pushes only transfer diffs. If a single LFS object exceeds the upload limit (typically 50 GB per object on the web UI), use ModelScope's Python SDK upload: ```bash pip install modelscope python3 -c " from modelscope.hub.api import HubApi api = HubApi(); api.login('<your_access_token>') api.upload_folder( repo_id='<owner>/build_AWSIM_map', folder_path='.', repo_type='dataset', commit_message='Initial dataset upload', )" ``` The SDK chunks large files automatically and is more robust over flaky links than a raw `git push`. ## About the TM99 sample **TM99** stands for **T**ianmen **M**ountain **99**-Turn — the famously serpentine hill-climb road carved into the cliffs of Tianmen Mountain in Zhangjiajie, Hunan, China. The bag in `data/TM99_uphill/` is one full uphill run captured with an RTK-GPS / SPAN-INS / Hesai-LiDAR / dual-camera stack: - **31 minutes** of recording, **1.94 M** messages across **17 topics** - **10.74 km** path, **715 m** elevation gain (327.9 m → 1043.4 m) - 174 k INSPVAX poses (≈100 Hz, RTK-fixed most of the time) - 18 k LiDAR scans (Hesai 64-line, 115 k points / scan, ≈3 MB each) - 18 k H.264-compressed images per camera (front + secondary) - IMU (raw + corrected) and EKF-fused pose at 100–1000 Hz The TM99 dataset was **collected and provided by [Whale Dynamic](https://whaledynamic.com/)**, a sponsor of the **Hitch Open World AI Championships**. It is included here (under `data/TM99_uphill/`) as a runnable end-to-end sample for the pipeline. ## Credits This project was designed and is maintained by **Dr. Allen Y. Yang** (Hitch Interactive · University of California, Berkeley). Sample datasets (TM99 uphill run under `data/TM99_uphill/` and TM99 high-resolution SLAM dataset under `map/TM99_high_res/`): **Whale Dynamic**, contributed in support of the **Hitch Open World AI Championships**. ## License This repository — including the source bags under `data/TM99_uphill/` and `data/TM99_downhill/`, the prebuilt maps under `map/TM99_uphill/` and `map/TM99_high_res/`, and all derived artefacts — is licensed by **Hitch Interactive** under the Creative Commons Attribution-NonCommercial 4.0 International license (CC BY-NC 4.0). See `LICENSE` for details. The TM99_uphill and TM99_high_res sample datasets retain their original attribution to Whale Dynamic, who collected and provided the underlying sensor recordings. They are redistributed here under Hitch Interactive's CC BY-NC 4.0 license for non-commercial use.



