Appearance
Navigation System
The navigation system is the core feature of the mobile app. It combines a graph-based pathfinding engine with real-time BLE positioning and a Flame-based map renderer.
Navigation Screen
File: ariadne/lib/screens/navigation_screen.dart
The main UI screen that coordinates all navigation components:
UI Layout
- Top bar: Location name, floor selector menu
- POI chips: Horizontal list of Point of Interest beacons for destination selection
- Map area: Full-screen Flame
GameWidgetwith the floor map - Floor bar: Current floor name and coordinates
- Bottom bar: BLE toggle, Navigate/Stop button, "Center on me" button
Lifecycle
- Build navigation graph from bundle floors
- Load beacons into the positioning engine
- Load the first floor map into the Flame game (end-user PNG if cached)
- User selects a floor →
_loadFloor(index)updates the game - User selects a POI → starts navigation
Navigation Graph
File: ariadne/lib/core/navigation_graph.dart
An undirected weighted graph built from the floor nodes and edges in the navigation bundle.
Building
buildFromFloors(floors) processes all NavigationFloor data:
- Adds all nodes to a lookup map
- Builds an adjacency list with bidirectional edges and weights
Pathfinding
findShortestPath(startId, endId) implements Dijkstra's algorithm using a HeapPriorityQueue (min-heap). Returns a PathResult with the ordered list of nodes and total weight.
Spatial Queries
findNearestNode(x, y, {floorId})— Finds the closest node to a point using Euclidean distance, optionally filtered by floorprojectOntoNearestEdge(px, py, {floorId})— Projects a point onto the nearest graph edge segment (used during live navigation to snap the user position to the path)
Map Renderer
File: ariadne/lib/game/navigation_game.dart
A Flame game that renders the indoor map with interactive pan/zoom.
Components
- Floor map image: Cached PNG; prefer
floor_{id}_enduser.pngwhen present, otherwise the deployment map - Nodes: Brand-color circles at graph node positions
- Edges: Green translucent lines between connected nodes
- Navigation path: Brand-color highlighted lines along the computed route
- Destination pin: Red marker at the target node
- Player marker: Live magnetometer heading plus the floor’s
compass_angle. Arrow when a compass is present; canvas pin (no rotation) when it is not
Controls
- Pinch zoom: Scale the map within defined bounds
- Pan: Drag to scroll the map
- Center:
centerOn(x, y)moves the camera to a specific point
Key Methods
| Method | Description |
|---|---|
loadFloorMap(navFloor, imagePath) | Load a floor's map image and draw its graph |
drawNavigationPath(path) | Highlight the route with brand-color lines and a destination pin |
updatePlayerPosition(x, y, angle, {hasCompass}) | Move the player marker; rotate the arrow when a compass is present |
centerOn(x, y) | Center the camera on a point |
Navigation Flow
- User selects a POI destination from the chips
- App requires a position fix (at least one BLE beacon detected)
- Finds the nearest graph node to the current position
- Finds the nearest graph node to the destination beacon
- Runs pathfinding:
- Server pathfinding (if connected):
POST /api/v1/navigation/pathfind - Local pathfinding (offline):
NavigationGraph.findShortestPath()
- Server pathfinding (if connected):
- Draws the path on the map via
NavigationGame.drawNavigationPath() - During navigation, position updates every 2 seconds:
- BLE scan →
PositioningEngine.calculatePosition() - 3+ beacon RSSI distances use floor
map_scale(cm) andruler_width(px):pixels_per_meter = 100 * ruler_width / map_scale(50 px/m if either is missing) - Project position onto nearest graph edge
- Update player x/y; heading comes from the last compass event (not
0)
- BLE scan →
- Compass events update heading immediately: device degrees + floor
compass_angle− 90°, wrapped to[0, 360)