# 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 `/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.