Skip to content

Range Finder

In this guide, you will learn how to implement route range calculation from a Point of Interest (POI) using the gem_kit package. This example demonstrates how to display a map, tap on a landmark, and calculate route ranges based on different transport modes and preferences.

route_ranges - example flutter screenshot

Setup

First, get an API key token, see the Getting Started guide.

Prerequisites

Make sure you completed the Environment Setup - Flutter Examples guide before starting this guide.

Build and Run

Start a terminal/command prompt and navigate to the route_ranges directory within the Flutter examples directory. This is the name of the example project.

Note - the gem_kit directory containing the Maps SDK for Flutter should be in the plugins directory of the example, e.g. example_pathname/plugins/gem_kit - see the environment setup guide above.

Run: flutter pub get

Configure the native parts:

First, verify that the ANDROID_SDK_ROOT environment variable is set to the root path of your android SDK.

In android/build.gradle add the maven block as shown, within the allprojects block, for both debug and release builds:

1    allprojects {
2        repositories {
3            google()
4            mavenCentral()
5            maven {
6               url "${rootDir}/../plugins/gem_kit/android/build"
7            }
8        }
9    }

in android/app/build.gradle within the android block, in the defaultConfig block, the android SDK version minSdk must be set as shown below.

Additionally, for release builds, in android/app/build.gradle, within the android block, add the buildTypes block as shown:

Replace example_pathname with the actual project pathname

 1android {
 2    defaultConfig {
 3        applicationId "com.magiclane.gem_kit.examples.example_pathname"
 4        minSdk 21
 5        targetSdk flutter.targetSdk
 6        versionCode flutterVersionCode.toInteger()
 7        versionName flutterVersionName
 8    }
 9    buildTypes {
10        release {
11            minifyEnabled false
12            shrinkResources false
13
14            // TODO: Add your own signing config for the release build.
15            // Signing with the debug keys for now, so `flutter run --release` works.
16            signingConfig signingConfigs.debug
17        }
18    }
19}

In the ios/Podfile configuration file, at the top, set the minimum ios platform version to 14 like this:

platform :ios, '14.0'

We recommend you to run these commands after you copy the gem_kit into your project: flutter clean flutter pub get and cd ios pod install

Then run the project:

flutter run --debug
or
flutter run --release

How it Works

This example demonstrates how to use the gem_kit package to display a map, tap on a landmark, and calculate route ranges based on different transport modes and preferences.

route_ranges - example flutter screenshot

Import Necessary Packages

First, import the required packages in your Dart code.

1import 'package:gem_kit/core.dart';
2import 'package:gem_kit/map.dart';
3import 'ranges_panel.dart';
4import 'package:flutter/material.dart';

Initialize GemKit

In the main function, initialize GemKit with your project API token.

1Future<void> main() async {
2  const projectApiToken = String.fromEnvironment('GEM_TOKEN');
3  await GemKit.initialize(appAuthorization: projectApiToken);
4  runApp(const MyApp());
5}

Build the Main Application

Define the main application widget, MyApp.

 1class MyApp extends StatelessWidget {
 2  const MyApp({super.key});
 3
 4  @override
 5  Widget build(BuildContext context) {
 6    return const MaterialApp(
 7      debugShowCheckedModeBanner: false,
 8      title: 'Range Finder',
 9      home: MyHomePage(),
10    );
11  }
12}

Handle Maps and Routes in the Stateful Widget

route_ranges - example flutter screenshot

route_ranges - example flutter screenshot

Create the stateful widget, MyHomePage, which will handle the map and routing functionality.

1class MyHomePage extends StatefulWidget {
2  const MyHomePage({super.key});
3
4  @override
5  State<MyHomePage> createState() => _MyHomePageState();
6}

Define State Variables and Methods

Within _MyHomePageState, define the necessary state variables and methods to interact with the map and manage routes.

 1class _MyHomePageState extends State<MyHomePage> {
 2  late GemMapController _mapController;
 3  Landmark? _focusedLandmark;
 4
 5  @override
 6  void dispose() {
 7    GemKit.release();
 8    super.dispose();
 9  }
10
11  @override
12  Widget build(BuildContext context) {
13    return Scaffold(
14      appBar: AppBar(
15        toolbarHeight: 50,
16        backgroundColor: Colors.deepPurple[900],
17        title: const Text('Range Finder', style: TextStyle(color: Colors.white)),
18      ),
19      body: Stack(
20        children: [
21          GemMap(
22            onMapCreated: _onMapCreated,
23          ),
24          if (_focusedLandmark != null)
25            Align(
26                alignment: Alignment.bottomCenter,
27                child: RangesPanel(
28                  onCancelTap: _onCancelLandmarkPanelTap,
29                  landmark: _focusedLandmark!,
30                  mapController: _mapController,
31                ))
32        ],
33      ),
34    );
35  }
36
37  void _onMapCreated(GemMapController controller) {
38    _mapController = controller;
39    _registerLandmarkTapCallback();
40  }
41
42  void _registerLandmarkTapCallback() {
43    _mapController.registerTouchCallback((pos) async {
44      _mapController.setCursorScreenPosition(pos);
45      final landmarks = _mapController.cursorSelectionLandmarks();
46
47      if (landmarks.isEmpty) {
48        return;
49      }
50
51      _mapController.activateHighlight(landmarks);
52      final lmk = landmarks[0];
53      setState(() {
54        _focusedLandmark = lmk;
55      });
56
57      _mapController.centerOnCoordinates(lmk.coordinates);
58    });
59  }
60
61  void _onCancelLandmarkPanelTap() {
62    _mapController.deactivateAllHighlights();
63    _mapController.preferences.routes.clear();
64    setState(() {
65      _focusedLandmark = null;
66    });
67  }
68}

Range Calculation and Preferences

The RangesPanel widget handles the UI and logic for calculating and displaying route ranges. Here are the critical parts:

Define State Variables

Define state variables to hold user preferences and the calculated route ranges.

 1class _RangesPanelState extends State<RangesPanel> {
 2  int _rangeValue = 3600;
 3  RouteTransportMode _transportMode = RouteTransportMode.car;
 4  RouteType _routeType = RouteType.fastest;
 5  bool _avoidMotorways = false;
 6  bool _avoidTollRoads = false;
 7  bool _avoidFerries = false;
 8  bool _avoidUnpavedRoads = false;
 9  BikeProfile _bikeProfile = BikeProfile.city;
10  double _hillsValue = 0;
11  TrafficAvoidance _trafficAvoidance = TrafficAvoidance.roadblocks;
12  List<Range> routeRanges = [];

Calculate Route Ranges

Use the RoutingService to calculate route ranges based on user preferences.

 1void _onAddRouteRangeButtonPressed(BuildContext context) {
 2  if (!_doesRouteRangeExist()) {
 3    _showSnackBar(context, message: "The route is being calculated.");
 4
 5    RoutingService.calculateRoute([widget.landmark], _getRoutePreferences(),
 6        (err, routes) {
 7      ScaffoldMessenger.of(context).clearSnackBars();
 8
 9      if (err == GemError.success) {
10        final routesMap = widget.mapController.preferences.routes;
11        final randomColor = Color.fromARGB(128, Random().nextInt(200),
12            Random().nextInt(200), Random().nextInt(200));
13        RouteRenderSettings settings =
14            RouteRenderSettings(fillColor: randomColor);
15        routesMap.add(routes!.first, true, routeRenderSettings: settings);
16        _centerOnRouteRange(routes.first);
17
18        setState(() {
19          _addNewRouteRange(routes.first, randomColor);
20        });
21      }
22    });
23
24    setState(() {});
25  }
26}

Define Route Preferences

Create a method to build route preferences based on user inputs.

 1RoutePreferences _getRoutePreferences() {
 2  switch (_transportMode) {
 3    case RouteTransportMode.car:
 4      return RoutePreferences(
 5        avoidMotorways: _avoidMotorways,
 6        avoidTollRoads: _avoidTollRoads,
 7        avoidFerries: _avoidFerries,
 8        avoidUnpavedRoads: _avoidUnpavedRoads,
 9        transportMode: _transportMode,
10        routeType: _routeType,
11        routeRanges: [_rangeValue],
12      );
13    case RouteTransportMode.lorry:
14      return RoutePreferences(
15        avoidMotorways: _avoidMotorways,
16        avoidTollRoads: _avoidTollRoads,
17        avoidFerries: _avoidFerries,
18        avoidUnpavedRoads: _avoidUnpavedRoads,
19        transportMode: _transportMode,
20        routeType: _routeType,
21        routeRanges: [_rangeValue],
22        avoidTraffic: _trafficAvoidance,
23      );
24    case RouteTransportMode.pedestrian:
25      return RoutePreferences(
26        avoidFerries: _avoidFerries,
27        avoidUnpavedRoads: _avoidUnpavedRoads,
28        transportMode: _transportMode,
29        routeRanges: [_rangeValue],
30      );
31    case RouteTransportMode.bicycle:
32      return RoutePreferences(
33        avoidFerries: _avoidFerries,
34        avoidUnpavedRoads: _avoidUnpavedRoads,
35        transportMode: _transportMode,
36        routeType: _routeType,
37        routeRanges: [_rangeValue],
38        avoidBikingHillFactor: _hillsValue,
39        bikeProfile: BikeProfileElectricBikeProfile(
40            profile: _bikeProfile, eProfile: ElectricBikeProfile()),
41      );
42    default:
43      return RoutePreferences();
44  }
45}

Handle User Interactions

Methods to manage user interactions, such as deleting, toggling, and centering on route ranges.

 1void _deleteRouteRange(int index) {
 2  widget.mapController.preferences.routes.remove(routeRanges[index].route);
 3  setState(() {
 4    routeRanges.removeAt(index);
 5  });
 6}
 7
 8void _toggleRouteRange(int index) {
 9  if (routeRanges[index].isEnabled) {
10    widget.mapController.preferences.routes.remove(routeRanges[index].route);
11    return;
12  } else {
13    RouteRenderSettings settings =
14        RouteRenderSettings(fillColor: routeRanges[index].color);
15    widget.mapController.preferences.routes
16        .add(routeRanges[index].route, true, routeRenderSettings: settings);
17    _centerOnRouteRange(routeRanges[index].route);
18  }
19}
20
21void _centerOnRouteRange(Route route) {
22  const appbarHeight = 50;
23  const padding = 20;
24
25  widget.mapController.centerOnRoute(route,
26      screenRect: RectType(
27        x: 0,
28        y: (appbarHeight + padding * MediaQuery.of(context).devicePixelRatio)
29            .toInt(),
30        width: (MediaQuery.of(context).size.width *
31                MediaQuery.of(context).devicePixelRatio)
32            .toInt(),
33        height: ((MediaQuery.of(context).size.height / 2 -
34                    appbarHeight -
35                    2 * padding * MediaQuery.of(context).devicePixelRatio) *
36                MediaQuery.of(context).devicePixelRatio)
37            .toInt(),
38      ));
39}
40
41void _showSnackBar(BuildContext context,
42    {required String message, Duration duration = const Duration(hours: 1)}) {
43  final snackBar = SnackBar(
44    content: Text(message),
45    duration: duration,
46  );
47
48  ScaffoldMessenger.of(context).showSnackBar(snackBar);
49}

You can start calculating a range by tapping the + button after adjusting your specifications for the routes.

Flutter Examples

Maps SDK for Flutter Examples can be downloaded or cloned with Git