store/mapStore
Zustand store for all map element state, with undo/redo via zundo temporal middleware.
useMapStore
const useMapStore: UseBoundStore<StoreApi<MapState>>Primary React hook for accessing map state.
// Read state
const lanes = useMapStore((s) => s.lanes)
const project = useMapStore((s) => s.project)
// Call actions
const { addElement, connectLanes } = useMapStore()For event handlers (to avoid stale closures):
const state = useMapStore.getState()
state.addElement(newLane)State
interface MapState {
project: ProjectConfig | null
lanes: Record<string, LaneFeature>
junctions: Record<string, JunctionFeature>
signals: Record<string, SignalFeature>
stopSigns: Record<string, StopSignFeature>
crosswalks: Record<string, CrosswalkFeature>
clearAreas: Record<string, ClearAreaFeature>
speedBumps: Record<string, SpeedBumpFeature>
parkingSpaces: Record<string, ParkingSpaceFeature>
roads: Record<string, RoadDefinition>
}All element collections are keyed by element ID.
Actions
setProject
setProject(config: ProjectConfig): voidSets the project metadata (name, origin lat/lon, version, date).
addElement
addElement(element: MapElement): voidAdds an element to the appropriate collection. For lane type, merges default lane properties before storing:
state.lanes[element.id] = { ...defaultLaneProps, ...element }Default lane props: width: 3.75, speedLimit: 13.89, laneType: CITY_DRIVING, etc.
updateElement
updateElement(element: MapElement): voidMerges updated properties into an existing element. Does nothing if the element ID does not exist.
Object.assign(state.lanes[element.id], element)removeElement
removeElement(id: string, type: MapElement['type']): voidDeletes the element and cleans up all cross-references:
- For lanes: removes
idfrom every other lane'spredecessorIds,successorIds,leftNeighborIds,rightNeighborIds
connectLanes
connectLanes(fromId: string, toId: string): voidEstablishes a successor/predecessor link:
lanes[fromId].successorIdsgainstoIdlanes[toId].predecessorIdsgainsfromId
Idempotent — duplicate IDs are not added.
setLaneNeighbor
setLaneNeighbor(laneId: string, neighborId: string, side: 'left' | 'right'): voidEstablishes a bilateral neighbor link:
side = 'left':laneId.leftNeighborIds←neighborId,neighborId.rightNeighborIds←laneIdside = 'right':laneId.rightNeighborIds←neighborId,neighborId.leftNeighborIds←laneId
addRoad
addRoad(road: RoadDefinition): voidAdds a road definition to the roads collection.
updateRoad
updateRoad(road: RoadDefinition): voidUpdates an existing road definition by ID.
removeRoad
removeRoad(roadId: string): voidDeletes a road definition and unassigns all lanes that referenced it.
assignLaneToRoad
assignLaneToRoad(laneId: string, roadId: string): voidSets lane.roadId = roadId for the specified lane.
unassignLaneFromRoad
unassignLaneFromRoad(laneId: string): voidClears lane.roadId for the specified lane.
clear
clear(): voidResets all element collections to {}. Does not reset project.
loadState
loadState(state: Partial<MapState>): voidBulk-assigns state fields. Used by the import flow to restore all collections at once:
loadState({
lanes: Object.fromEntries(parsed.lanes.map(l => [l.id, l])),
junctions: Object.fromEntries(...),
// ...
})Undo / Redo
useTemporalMapStore is the temporal store exposed by zundo:
import { useMapStore } from './mapStore'
const useTemporalMapStore = (useMapStore as any).temporal
// In a component:
const { undo, redo, pastStates, futureStates } = useTemporalMapStore()Each set() call inside any action creates a new history snapshot. The entire MapState is snapshotted — undo/redo is O(n) in state size, not O(diff).