Skip to main content

Flutter Native - Itinerary

This guide explains how to add, display, and manage itineraries (routes) on the Wemap map. Itineraries are indoor/outdoor routes computed by the Wemap backend—from user location or chosen points to a destination. You can customize search rules (e.g. wheelchair-accessible) and listen to itinerary events. To start turn-by-turn navigation along an itinerary, see Navigation.


Overview​

The itinerary feature lets you:

  1. Add an itinerary — From user location, map center, or custom origin to a destination
  2. Display the route — The route is drawn on the map automatically
  3. Show the itinerary panel — Optional built-in ItineraryWidget with duration, arrival time, and a button to start navigation

Typical flow: user selects a POI → taps "Go there" in DetailedView → itinerary is added → ItineraryWidget appears → user taps "Start" to begin navigation (see Navigation).


Adding itineraries​

All itinerary methods are on MapManager, available in onMapReady:

WemapMap(
options: MapOptions(...),
onMapReady: (MapData mapData, MapManager mapManager) {
// Use mapManager for itineraries
},
)

1. From user location​

Use when the start point is the user's current position (GPS or VPS). If location is unknown, locateMe is called automatically (or VPS scan is triggered with VPS).

Permissions: Methods with FromUserLocation need location permission (GPS) or camera permission (VPS) granted before calling. Request permissions first—see GPS and VPS.

await mapManager.addItineraryFromUserLocation(
destination: Coordinate(
latitude: 48.876,
longitude: 2.323,
level: 0,
),
);

With search rules and optional color:

await mapManager.addItineraryFromUserLocation(
destination: poi.coordinate,
searchRules: ItinerarySearchRules(ruleNames: ["wheelchair"]),
color: Colors.blue,
updateUserLocation: false, // true to force re-locate before computing
);

VPS note: If the user is not yet localized, passing updateUserLocation: true triggers a scan. The itinerary is added once localization succeeds.

2. From map center​

Use when the user chooses a point on the map as the start (e.g. by moving and zooming).

await mapManager.addItineraryFromMapCenter(
destination: poi.coordinate,
searchRules: ItinerarySearchRules(ruleNames: ["wheelchair"]),
color: Colors.green,
);

3. From explicit origin to destination​

Use when both start and end are known coordinates:

await mapManager.addItinerary(
origin: Coordinate(latitude: 48.877, longitude: 2.324, level: 0),
destination: Coordinate(latitude: 48.876, longitude: 2.323, level: 1),
searchRules: ItinerarySearchRules(),
color: Colors.red,
);

Removing itineraries​

To clear the current itinerary and route from the map:

await mapManager.removeItinerary();

Search rules (accessibility & constraints)​

Search rules influence how the route is computed (e.g. avoiding stairs, prefer elevators).

Wheelchair (PRM) rule​

searchRules: ItinerarySearchRules.WHEELCHAIR

Custom rule names​

Rules are map-specific. Get available rule names from the current map:

final ruleNames = await mapManager.searchRuleNames();
// e.g. ["wheelchair", "avoid_escalators", "prefer_elevator", ...]

Use them when adding an itinerary:

await mapManager.addItineraryFromUserLocation(
destination: destination,
searchRules: ItinerarySearchRules(ruleNames: ["avoid_escalators", "prefer_elevator"]),
);

Callbacks​

Listen to itinerary events via WemapMap:

CallbackWhen triggered
onItineraryAddedItinerary computed and added to map
onItineraryRemovedItinerary removed
onItineraryFailedItinerary computation failed (e.g. no route found)
WemapMap(
options: MapOptions(...),
onItineraryAdded: (Itinerary itinerary) {
print('Route: ${itinerary.distance}m, ${itinerary.duration}s');
},
onItineraryRemoved: () {
print('Itinerary cleared');
},
onItineraryFailed: (String error) {
print('Failed: $error');
// Show error to user (e.g. "No route to this point")
},
)

Itinerary model​

The Itinerary object returned in onItineraryAdded contains:

FieldTypeDescription
fromCoordinateStart point
toCoordinateEnd point
durationdoubleTotal duration in seconds
distancedoubleTotal distance in meters
legsList<Leg>Route legs

Each Leg has duration, distance, and steps. Each Step includes:

  • name, number, distance, duration, isLastStep
  • kind — Kind enum: STAIRS, ELEVATOR, ESCALATOR, TURN, CONTINUE, etc.
  • direction — Direction enum: LEFT, RIGHT, STRAIGHT, UP, DOWN, etc.
  • navInstructions — Turn-by-turn instructions

Built-in itinerary UI​

OptionDescription
itineraryWidgetEnabled: trueShows ItineraryWidget when an itinerary is added — duration, arrival time, start/end, PRM toggle, rules panel, back button, and "Start" to begin navigation
detailedViewEnabled: trueEnables "Go there" button in POI detail view, which triggers addItineraryFromUserLocation

See Widgets for screenshots. To start navigation when the user taps "Start", see Navigation.

Customizing the built-in itinerary panel​

Partial customization — keep the default layout, restyle sections:

itineraryWidgetBuilder: (context, data) => data.build(
prmSwitchBuilder: (context, prm) => Switch(
value: prm.isEnabled,
onChanged: prm.onChanged,
),
),

Full customization — build your own UI with the same logic as the SDK widget. Use [ItineraryWidgetController] from data.controller; only its documented getters and action methods are part of the public API:

itineraryWidgetBuilder: (context, data) {
final controller = data.controller;
return ListenableBuilder(
listenable: controller,
builder: (context, _) => Column(
children: [
Text('${controller.startPointLabel} → ${controller.endPointLabel}'),
if (controller.isCalculating)
const CircularProgressIndicator()
else
Text('${controller.itineraryMinutes} min'),
IconButton(
onPressed: controller.swapPoints,
icon: const Icon(Icons.swap_vert),
),
IconButton(
onPressed: controller.close,
icon: const Icon(Icons.close),
),
if (controller.showStartNavigationButton)
ElevatedButton(
onPressed: controller.startNavigation,
child: const Text('Start'),
),
],
),
);
},

Inside itineraryWidgetBuilder, read or update rules via controller.selectedRuleNames, controller.applyRuleNames(...), or controller.setRuleEnabled(...).


Custom integration (no built-in ItineraryWidget)​

You can handle itineraries entirely in your own UI:

// 1. Add itinerary when user selects a destination
mapManager.addItineraryFromUserLocation(destination: selectedPOI.coordinate);

// 2. React to success/failure
onItineraryAdded: (Itinerary itinerary) {
setState(() {
_currentItinerary = itinerary;
_showCustomRoutePanel = true;
});
},
onItineraryFailed: (String error) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(error)));
},

// 3. Your custom "Start" button — starts turn-by-turn
ElevatedButton(
onPressed: () => mapManager.startNavigationWithCurrentItinerary(),
child: Text('Start'),
)

Coordinate and level​

For indoor maps, include level in Coordinate when known:

Coordinate(
latitude: 48.876,
longitude: 2.323,
level: 1, // Floor level
)

Complete example​

WemapMap(
options: MapOptions(
mapID: yourMapId,
token: yourToken,
environment: Environment.PROD
),
detailedViewEnabled: true,
itineraryWidgetEnabled: true,
onMapReady: (mapData, mapManager) async {
_mapManager = mapManager;
_availableRules = await mapManager.searchRuleNames();
// Optionally add itinerary programmatically:
// await mapManager.addItinerary(
// origin: somePOI.coordinate,
// destination: anotherPOI.coordinate,
// searchRules: ItinerarySearchRules(ruleNames: ["wheelchair"]),
// );
},
onItineraryAdded: (itinerary) {
print('Added: ${itinerary.duration}s, ${itinerary.distance}m');
},
onItineraryFailed: (error) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(error)));
},
)

Troubleshooting​

IssueSolution
FromUserLocation methods fail or hangGrant location (GPS) or camera (VPS) permission before calling; see GPS and VPS
No route found / onItineraryFailedDestination may be unreachable; try different searchRules or ensure user/destination are on the map graph
Itinerary not drawnEnsure addItinerary* completed successfully and onItineraryAdded fired
"Go there" does nothingEnable detailedViewEnabled; check location permission (GPS) or VPS state
Rules not appliedUse mapManager.searchRuleNames() to get valid rule names for your map