chore(task): archive 08-07-qgis-traffic-signal-overrides

This commit is contained in:
2026-08-07 12:51:10 +08:00
parent e153a1c57d
commit 208ed055ac
7 changed files with 271 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
{"file":".trellis/spec/pipeline/cli-and-stages.md","reason":"Check stage ownership, diagnostics, and traffic signal contract compliance"}
{"file":".trellis/spec/pipeline/external-tools.md","reason":"Check atomic reimport behavior and external-tool handling"}
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"Check full editable-layer to runtime JSON to Blender/Cesium data flow"}
{"file":".trellis/spec/guides/artifact-parity-guide.md","reason":"Check intended and unintended scene/GLB structural differences"}
{"file":".trellis/spec/blender/testing.md","reason":"Check appropriate pure and Blender validation coverage"}

View File

@@ -0,0 +1,100 @@
# Design: QGIS Traffic Signal Overrides
## Architecture
Introduce a separate auxiliary-edit-layer registry rather than adding traffic signals to `SCENE_LAYERS`. The initial registry contains one layer:
```text
traffic_signal_assemblies.geojson
geometry: Point (pole ground position, EPSG:4326)
properties: stable identity, source identity, heading, phase, reach, stop point, enabled, z offset
```
The existing runtime file remains:
```text
traffic_signals.json
version/layout/signals[] with full pose.* data
```
The editable GeoJSON is the placement source; the runtime JSON is a derived consumer artifact.
## Data Flow
```text
OSM controls + topology + stop lines + intersections
|
intermediates only
v
traffic_signal_assemblies.geojson
|
import into GeoPackage
|
edit in QGIS
|
reimport
v
traffic_signal_assemblies.geojson
|
validate + derive pose
v
traffic_signals.json
/ \
Blender preview/Cesium
```
`blender` reruns the final validation/derivation arrow from the editable GeoJSON so derived JSON cannot be stale, but it never reruns the OSM initialization arrow.
## Editable Feature Contract
Recommended properties:
| Property | Type | Ownership |
|---|---|---|
| `signal_uid` | string | generated, immutable technical identity |
| `display_id` | string | user-editable unique label/number |
| `control_id` | string | generated OSM control id |
| `approach_id` | string | generated physical approach identity |
| `source_way_id` | string | generated matching/diagnostic field |
| `heading_deg` | number | user-editable assembly facing direction |
| `phase_group` | integer 0/1 | user-editable current two-phase group |
| `mast_reach_m` | positive number | user-editable arm reach |
| `stop_lon`, `stop_lat` | finite numbers | generated vehicle stop point, preserved when pole moves |
| `enabled` | boolean/integer | user-editable suppression flag |
| `z_offset_m` | finite number | user-editable vertical adjustment |
Point geometry is the pole longitude/latitude. The runtime `id` should be derived from `signal_uid`, not display numbering, so changing `display_id` does not rename GLB nodes or break preview control.
## Stable Identity
Extend parsed OSM arm data to retain enough deterministic source identity (control node, way, adjacent arm direction/node). Generate a technical key from those source values. Do not use sorted array index or rounded heading as the primary key.
If topology changes on a future `intermediates` run, the rebuilt GeoPackage may produce new identities. This is consistent with current road-edit lifecycle and is explicitly out of scope for MVP migration. Validation still reports duplicate identities and malformed source fields.
## QGIS Integration
- Add an auxiliary layer definition separate from the nine render layers.
- Import it into the same GeoPackage after render layers.
- Include it in the generated project but exclude it from the merged scene and 2D raster preview unless deliberately enabled for editing visibility.
- Use a point marker plus rotated direction indicator driven by `heading_deg` and label by `display_id`, falling back to `signal_uid`.
- Configure read-only/editor widgets where practical: technical/source ids read-only; phase group constrained to 0/1; numeric fields constrained to valid ranges; enabled as checkbox.
## Reimport and Atomicity
Extend the reimport layer manifest to include auxiliary editable layers while keeping render-scene merge derived only from `SCENE_LAYERS`. Export every layer into staging, parse and validate all editable features, then replace output files. Runtime JSON is written only after the staged auxiliary layer passes validation.
## Compatibility
- The next `intermediates` run bootstraps existing areas; no old JSON migration is required.
- Main `.blend`/GLB geometry changes intentionally when a QGIS edit changes a signal.
- Dynamic and countdown GLBs continue using runtime signal ids, now stable across ordinary reimport edits.
- Current two-phase simulation remains unchanged.
## Risks and Controls
- **OSM way splitting changes source ids:** accepted across a full intermediates rebuild; ordinary reimport is stable.
- **QGIS boolean/string coercion:** normalize known GDAL representations before strict validation and test the round-trip output.
- **Accidental source-field editing:** mark technical fields read-only in QGIS and validate identity format during reimport.
- **Partial overwrite on invalid auxiliary data:** retain the existing staging-before-replace discipline.
- **Old spec conflict:** update pipeline specs that currently forbid traffic-signal anchors in GeoPackage, clarifying the distinction between editable assembly points and derived runtime anchors.

View File

@@ -0,0 +1,6 @@
{"file":".trellis/spec/pipeline/cli-and-stages.md","reason":"Traffic signal stage ownership, reimport lifecycle, manifests, and current anchor contract"}
{"file":".trellis/spec/pipeline/layer-registry.md","reason":"Keep the auxiliary editable layer separate from the nine render layers and preserve their order"}
{"file":".trellis/spec/pipeline/external-tools.md","reason":"GeoPackage import/export and staging-before-replace requirements"}
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"OSM to GeoJSON/GPKG to Blender/Cesium contract review"}
{"file":".trellis/spec/blender/asset-generation.md","reason":"Signal pose and dynamic asset generation constraints"}
{"file":".trellis/spec/preview/vehicle-routes.md","reason":"Vehicle stop coordinates and runtime signal data coupling"}

View File

@@ -0,0 +1,53 @@
# Implementation Plan: QGIS Traffic Signal Overrides
## 1. Contracts and Pure Logic
- [x] Add an auxiliary editable-layer definition without modifying `SCENE_LAYERS` ordering.
- [x] Extend OSM arm parsing with deterministic approach identity inputs.
- [x] Split traffic-signal logic into automatic editable-feature generation, feature validation/normalization, and runtime pose derivation.
- [x] Use stable technical ids for runtime signal ids; keep `display_id` as editable metadata.
- [x] Add pure Node tests for T/cross counts, stable ids, movement/heading reconstruction, disabled features, duplicate ids, and invalid values.
## 2. Intermediates and QGIS
- [x] Write `traffic_signal_assemblies.geojson` after stop-line/intersection outputs are stable.
- [x] Import the auxiliary point layer into the GeoPackage after the nine render layers.
- [x] Extend generated QGIS project code with point/direction styling, labels, and field widgets/constraints.
- [x] Confirm the auxiliary layer is excluded from merged road scene ordering and raster preview behavior.
## 3. Reimport and Stage Ownership
- [x] Extend `reimport-gpkg.js` to discover/export render and auxiliary layers through staging.
- [x] Validate the staged editable layer before replacing any output artifact.
- [x] Rebuild runtime `traffic_signals.json` from editable GeoJSON after `intermediates`, `reimport`, and at Blender entry.
- [x] Remove Blender-entry OSM placement regeneration so QGIS edits remain authoritative.
- [x] Extend stage manifests and diagnostics with auxiliary input/output records and feature counts.
## 4. Cross-Layer Consumers
- [x] Preserve `display_id` and stable runtime ids through Blender and Cesium metadata where useful.
- [x] Verify static signal objects, dynamic lenses, countdown nodes, and vehicle stop behavior all consume the same enabled runtime records.
- [x] Update pipeline specifications to replace the old prohibition with the editable-layer/derived-runtime distinction.
## 5. Validation
- [x] Run Node syntax checks and focused unit tests.
- [x] Run existing preview-assets, preflight, budget, and relevant pipeline tests.
- [x] Run `intermediates` and inspect the GeoPackage/QGIS project feature schema and styling.
- [ ] Make a controlled QGIS edit to one signal (display id, point, heading), run `reimport,blender,cesium,preview`, and verify only the intended assembly changes. (`reimport` and Blender passed; Cesium/preview refresh was not repeated.)
- [x] Confirm an invalid/duplicate edit fails before overwriting valid outputs.
- [ ] Inspect Blender/Cesium structural digests and Safari preview for T and cross junctions. (Blocked this run by Blender 4.5.12 Metal startup SIGSEGV before project Python.)
## Risky Files / Rollback Points
- `scripts/lib/traffic-signals.js`: identity and pose contract; land pure tests before pipeline integration.
- `scripts/build-osm2streets-qgis.js`: GeoPackage recreation and generated QGIS Python; verify auxiliary import independently before styling.
- `scripts/reimport-gpkg.js`: atomic overwrite boundary; preserve staging semantics.
- `scripts/build-area.js`: stage ownership; ensure Blender derives from editable GeoJSON rather than overwriting it.
- `blender/osmassets/traffic_signals.py` and preview runtime should require minimal or no geometry changes; unexpected edits here indicate contract leakage.
## Review Gate Before Start
- [ ] User approves the final planning summary.
- [ ] `prd.md`, `design.md`, and `implement.md` agree on full editable layer ownership and out-of-scope intermediates persistence.
- [ ] No unresolved product decision remains.

View File

@@ -0,0 +1,14 @@
# Debug Notes
## 2026-08-07 countdown node-name regression
Stable `signal_uid` values are intentionally descriptive and can exceed Blender's
63-byte object-name limit. Using them directly in dynamic lens/countdown node
names caused Blender to truncate names while Cesium looked up the untruncated
strings. The countdown GLBs then exposed all digits without the runtime being
able to hide the inactive values, appearing as overlapping/blurred numbers.
Runtime signal records now carry a deterministic short `nodeKey` (`ts_` plus
the first 16 hex characters of SHA-256 of `signal_uid`). Blender uses it for
dynamic object names and Cesium uses the same key for lookups. Preview keeps a
fallback to `signal.id` for older metadata files.

View File

@@ -0,0 +1,67 @@
# QGIS Traffic Signal Overrides
## Goal
Make every automatically generated vehicle traffic signal independently identifiable and editable in QGIS. A user must be able to assign a display number, move a pole, rotate its assembly, adjust supported placement attributes, run `reimport`, and have Blender and Cesium consume that edited result without OSM regeneration overwriting it.
## Background
- Current traffic signals are derived from OSM `highway=traffic_signals` controls, road topology, `vehicle_stop_lines.geojson`, and `intersection_surface.geojson` by `scripts/lib/traffic-signals.js`.
- Current sequential ids such as `signal-1` depend on generation order and are not suitable as persistent edit identities.
- Current `<geojsonDir>/traffic_signals.json` contains fully derived `pose.*` data but is deliberately excluded from the GeoPackage and QGIS project.
- Existing road editing establishes the desired lifecycle: `intermediates` initializes a GeoPackage, the user edits it in QGIS, and `reimport` exports the edited data back to GeoJSON. Running `intermediates` again may discard manual edits; that behavior remains explicit and unchanged.
## Requirements
### R1. Editable auxiliary layer
- `intermediates` must create a point FeatureCollection containing one feature per physical signal assembly and import it into the area GeoPackage.
- The generated QGIS project must expose the layer with a visible directional symbol and a label suitable for identifying individual signals.
- The auxiliary layer must not join `SCENE_LAYERS` or the merged road scene because it is an editing/control artifact, not a road render layer.
### R2. Stable identity and numbering
- Every generated feature must contain an immutable technical `signal_uid` derived deterministically from its OSM control and physical approach identity, rather than array order.
- Every feature must contain an editable `display_id` intended for user-facing numbering.
- Build/reimport validation must reject duplicate or missing `signal_uid` values and duplicate non-empty `display_id` values with an actionable error.
### R3. Editable placement contract
- Point geometry represents the pole ground position.
- Editable attributes must include at least `display_id`, `heading_deg`, `phase_group`, `mast_reach_m`, `enabled`, and `z_offset_m`.
- Source/control attributes required for matching and diagnostics must be preserved, including `control_id` and approach identity.
- Vehicle stop coordinates remain independent attributes; moving the pole must not silently move the vehicle stop point.
- After reimport, the pipeline must deterministically rebuild `pose.pole`, `pose.arm`, `pose.head`, `pose.lenses`, and `pose.countdown` from the edited point and attributes.
### R4. Stage ownership
- `intermediates` initializes the editable signal layer from current OSM/topology and derives the runtime `traffic_signals.json` from it.
- `reimport` must stage, validate, and export the editable signal layer along with the existing road layers, then rebuild the runtime JSON.
- `blender` must rebuild runtime `traffic_signals.json` from the current editable signal GeoJSON. It must not recompute signal placement directly from OSM and erase QGIS edits.
- `cesium` and `preview` continue consuming artifacts derived from the same runtime JSON and retain matching signal node ids.
### R5. Diagnostics and compatibility
- Invalid geometry, invalid numeric fields, duplicate identities, unsupported phase groups, and unmatched source references must fail before replacing valid output artifacts.
- `enabled=false` suppresses a signal without requiring feature deletion, so automatic regeneration cannot accidentally resurrect an intentionally disabled assembly within the same edit lifecycle.
- Existing areas without an editable signal layer must receive one on their next `intermediates` run. No migration of previously hand-edited traffic signal JSON is required.
## Acceptance Criteria
- [ ] A clean `intermediates` run creates the editable traffic-signal GeoJSON, a GeoPackage layer with the same feature count, and a QGIS project layer with labels and directional symbols.
- [ ] T junctions produce three editable features and cross junctions produce four, each with a unique deterministic `signal_uid`.
- [ ] Moving one point in QGIS and changing its `display_id` and `heading_deg`, followed by `reimport,blender,cesium,preview`, changes only that signal assembly's placement/identity-facing metadata while preserving its vehicle stop point.
- [ ] Re-running `blender` after reimport does not overwrite the QGIS-edited pole position or heading from OSM.
- [ ] Setting one feature to disabled removes its static and dynamic signal assets while leaving the other signals intact.
- [ ] Duplicate `signal_uid` or non-empty `display_id`, invalid geometry, and invalid placement fields abort reimport without partially replacing GeoJSON outputs.
- [ ] Blender/Cesium node counts and ids match the enabled features in the final runtime JSON; lights and countdowns continue switching correctly.
- [ ] Existing road GeoPackage import/reimport behavior and merged scene layer order remain unchanged.
- [ ] Unit/integration tests cover stable ids, editable-feature validation, override-to-pose reconstruction, auxiliary GeoPackage round-trip, and stage ownership.
## Out Of Scope
- Preserving QGIS edits across a subsequent full `intermediates` rebuild; as with road edits, users must preserve or reapply edits before regenerating the GeoPackage.
- A complete traffic-controller timing editor or arbitrary multi-phase signal program.
- Independent editing of each lens or countdown glyph position; those remain derived from the assembly point, heading, and shared layout.
- Automatically assigning a stable identity to a brand-new signal feature drawn manually in QGIS.

View File

@@ -0,0 +1,26 @@
{
"id": "qgis-traffic-signal-overrides",
"name": "qgis-traffic-signal-overrides",
"title": "QGIS traffic signal overrides",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-07",
"completedAt": "2026-08-07",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}