Skip to content

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.

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 GameWidget with the floor map
  • Floor bar: Current floor name and coordinates
  • Bottom bar: BLE toggle, Navigate/Stop button, "Center on me" button

Lifecycle ​

  1. Build navigation graph from bundle floors
  2. Load beacons into the positioning engine
  3. Load the first floor map into the Flame game (end-user PNG if cached)
  4. User selects a floor → _loadFloor(index) updates the game
  5. User selects a POI → starts navigation

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 floor
  • projectOntoNearestEdge(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.png when 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 ​

MethodDescription
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
  1. User selects a POI destination from the chips
  2. App requires a position fix (at least one BLE beacon detected)
  3. Finds the nearest graph node to the current position
  4. Finds the nearest graph node to the destination beacon
  5. Runs pathfinding:
    • Server pathfinding (if connected): POST /api/v1/navigation/pathfind
    • Local pathfinding (offline): NavigationGraph.findShortestPath()
  6. Draws the path on the map via NavigationGame.drawNavigationPath()
  7. During navigation, position updates every 2 seconds:
    • BLE scan → PositioningEngine.calculatePosition()
    • 3+ beacon RSSI distances use floor map_scale (cm) and ruler_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)
  8. Compass events update heading immediately: device degrees + floor compass_angle − 90°, wrapped to [0, 360)