Now we have a simple map where you can search for locations. When finishing this step you'll be able to create a directions between two points and change the transportation mode.
After having created our list of search results, we have a good starting point for creating directions between two Locations. Since our search only supports searching for a single location, we will hardcode a Location's coordinate into our app, and use that as the basis for our Origin. Then we'll create a route, show the navigation details, and display the route on the map.
We have already created a point in the basic example, called _userPosition to use as a starting point for directions on MapState. The position given with the example is for the main entrance to the White House, replace it with some location that fits your usecase.
final _userPosition =MPPoint.withCoordinates( longitude:-77.03740973527613, latitude:38.897389429704695, floorIndex:0);
We can extend the search functionality we made earlier by implementing the onLocationSelected listener on the MapsIndoorsWidget, and showing the selected location's details on the bottom sheet:
voidonMapControlReady(MPError? error) async {if (error ==null) { ... // Add a listener for location selection events, we do not want to stop the SDK from moving the camera, so we do not comsume the event
_mapControl ..setOnLocationSelectedListener(onLocationSelected, false) ..goTo(awaitgetDefaultVenue()); } else { ... }}
and implementing the onLocationSelected listener:
voidonLocationSelected(MPLocation? location) {// if no location is selected, close the sheetif (location ==null) { _controller?.close(); _controller =null;return; }// show location details _controller = _scaffoldKey.currentState?.showBottomSheet((context) {returnContainer( color:Colors.white, padding:constEdgeInsets.only(bottom:50.0, left:100, right:100), child:Column( mainAxisSize:MainAxisSize.min, children: [constSizedBox( height:30, ),Text(location.name),constSizedBox( height:30, ),Text("Description: ${location.description}"),constSizedBox( height:30, ),Text("Building: ${location.buildingName} - ${location.floorName}"),constSizedBox( height:30, ), ... ], ), ); }); _controller?.closed.then((value) => _mapControl.selectLocation(null));}
Now we will create a class that takes a origin and destination and can handle the controls of a route. This is a rather complex class that creates a route, lets us traverse it without errors and displays the steps of the route in the bottom sheet. A minimal example would only require the constructor, the leg selected listener and the UI.
First lets set up the constructor and services, we want to create the RouteHandler with an origin, destination and the state of our Scaffold (to create a bottom sheet later). We will also set up our MPDirectionsService and MPDirectionsRenderer and generate a route immediately. finally we show the route on the Renderer.
classRouteHandler {RouteHandler( {requiredMPPoint origin,requiredMPPoint destination,requiredScaffoldState scaffold}) { _service.getRoute(origin: origin, destination: destination).then((route) { _route = route; _renderer.setRoute(route);// Start some UI handling here later }); }final _service =MPDirectionsService();final _renderer =MPDirectionsRenderer();latefinalMPRoute _route;}
Now we want some controllers for the state of our route, we want a method to safely update the leg index, we want to listen to when the leg index changes and we want to be able to remove the route again. First we add a onLegSelectedListener to the class and set it on the renderer. Then we add a getter/setter for the current leg index, where we ensure that we cannot get an index that is out of bounds for the current route. and finally we add a removeRoute method that clears the route from the map.
classRouteHandler {RouteHandler( {requiredMPPoint origin,requiredMPPoint destination,requiredScaffoldState scaffold}) { _service.getRoute(origin: origin, destination: destination).then((route) { ... _renderer.setOnLegSelectedListener(onLegSelected);// Start some UI handling here later }); } ...// backing field for the current route leg indexint _currentIndex =0;intget currentIndex {return _currentIndex; }// clamp the index to be in the correct rangesetcurrentIndex(int index) { _currentIndex = index.clamp(0, _route.legs!.length -1); } // updates the state of the routehandler if the route is updated externally, eg. by tapping the next marker on the route
voidonLegSelected(int legIndex) { _controller?.setState!(() => currentIndex = legIndex); }// external handle to clear the routevoidremoveRoute() { _renderer.clear(); }}
Finally we want some UI interaction, so we add a ShowRoute method that creates a BottomSheet which contains some navigation icons, to move between legs of the route, and a text field to display the steps of the current leg. We also add a method to expand the route steps in a way that we can show the leg in a single string.
Then we call showRoute from our constructor. Additionally we close the BottomSheet when removing the map.
classRouteHandler {RouteHandler( {requiredMPPoint origin,requiredMPPoint destination,requiredScaffoldState scaffold}) { _service.setTravelMode(MPDirectionsService.travelModeDriving); _service.getRoute(origin: origin, destination: destination).then((route) { ...showRoute(scaffold); }); }PersistentBottomSheetController? _controller; ...// opens the route on a bottom sheetvoidshowRoute(ScaffoldState scaffold) { _controller = scaffold.showBottomSheet((context) {returnContainer( color:Colors.white, padding:constEdgeInsets.only(top:50.0, bottom:50.0), child:Row( mainAxisAlignment:MainAxisAlignment.center, mainAxisSize:MainAxisSize.min, children: [// goes a step back on the routeIconButton( onPressed: () async { currentIndex--;await _renderer.selectLegIndex(currentIndex); }, icon:constIcon(Icons.keyboard_arrow_left), iconSize:50, ),// displays the route instructionsExpanded( child:Text(expandRouteSteps(_route.legs![currentIndex].steps!), softWrap:true, textAlign:TextAlign.center, ), ),// goes a step forward on the routeIconButton( onPressed: () async { currentIndex++;await _renderer.selectLegIndex(currentIndex); }, icon:constIcon(Icons.keyboard_arrow_right), iconSize:50, ), ], ), ); });// if the bottom sheet is closed, then clear the route _controller?.closed.then((val) { _renderer.clear(); }); }// external handle to clear the routevoidremoveRoute() { _renderer.clear(); _controller?.close(); }// expands the step instructions into a single string for the entire legStringexpandRouteSteps(List<MPRouteStep> steps) {String sum ="${steps[0].maneuver}";for (final step in steps.skip(1)) { sum +=", ${step.maneuver}"; }return sum; }}
With this we now have a class that will handle creating a Route and displaying it on the map for us. You can see the entire implementation of RouteHandler here: main.dart
We can now add a button on our onLocationSelected bottomsheet to generate a route from _userPosition to the selected location:
final _scaffoldKey =GlobalKey<ScaffoldState>();RouteHandler? _routeHandler;PersistentBottomSheetController? _controller;voidonLocationSelected(MPLocation? location) { ...// if an active route is displayed, remove it from view _routeHandler?.removeRoute();// show location details _controller = _scaffoldKey.currentState?.showBottomSheet((context) {returnContainer( ... child:Column( mainAxisSize:MainAxisSize.min, children: [ ...// when clicked will create a route from the user position to the locationElevatedButton( onPressed: () => _routeHandler =RouteHandler( origin: _userPosition, destination: location.point, scaffold: _scaffoldKey.currentState!), child:constRow( children: [Icon(Icons.keyboard_arrow_left_rounded),SizedBox( width:5, ),Text("directions") ], ), ), ], ), ); }); _controller?.closed.then((value) => _mapControl.selectLocation(null));}
We can now show and navigate a route from our _userPosition to a selected location.
In MapsIndoors, the transportation mode is referred to as travel mode. There are four travel modes, walking, bicycling, driving and transit (public transportation). The travel modes generally applies for outdoor navigation. Indoor navigation calculations are based on walking travel mode.
To swap Travel Modes you set the Travel Mode before making a query for the route: