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.
Setup¶
Prerequisites¶
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}
Then run the project:
flutter run --debug
orflutter 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.
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¶
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.