# Range Finder

Last updated:

<!-- -->

October 18, 2026

<!-- -->

\| 6 minutes read

<br />

<br />

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

## How it Works[​](#how-it-works "Direct link to How it Works")

This example demonstrates the following features:

* Allow users to interact with a map by tapping landmarks to focus on specific Points of Interest (POIs).
* Perform route range calculations from selected POIs using preferences such as transport mode.

![](/docs/flutter/assets/images/example_flutter_range_finder1-4726503e877daf1f0fa8106a94ea0b61.png)

![](/docs/flutter/assets/images/example_flutter_range_finder2-a3c759f9124fe87514730082fc4ecac6.png)

### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration")

The following code creates a user interface featuring a `GemMap` widget and an app bar. When a Point of Interest (POI) is selected on the map, a range finder panel appears at the bottom of the screen.

Define the main application widget, MyApp.

main.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/range_finder/lib/main.dart?ref_type=heads#L20)

```dart
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Range Finder',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

```

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

main.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/range_finder/lib/main.dart?ref_type=heads#L40)

```dart
class _MyHomePageState extends State<MyHomePage> {
  late GemMapController _mapController;

  Landmark? _focusedLandmark;

  @override
  void dispose() {
    GemKit.release();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        toolbarHeight: 50,
        backgroundColor: Colors.deepPurple[900],
        title: const Text(
          'Range Finder',
          style: TextStyle(color: Colors.white),
        ),
      ),
      body: Stack(
        children: [
          GemMap(
            key: ValueKey("GemMap"),
            onMapCreated: _onMapCreated,
            appAuthorization: projectApiToken,
          ),
          if (_focusedLandmark != null)
            Align(
              alignment: Alignment.bottomCenter,
              child: RangesPanel(
                onCancelTap: _onCancelLandmarkPanelTap,
                landmark: _focusedLandmark!,
                mapController: _mapController,
              ),
            ),
        ],
      ),
    );
  }

  // The callback for when map is ready to use.
  Future<void> _onMapCreated(GemMapController controller) async {
    // Save controller for further usage.
    _mapController = controller;

    // Register route tap gesture callback.
    await _registerLandmarkTapCallback();
  }

  Future<void> _registerLandmarkTapCallback() async {
    _mapController.registerOnTouch((pos) async {
      // Select the object at the tap position.
      await _mapController.setCursorScreenPosition(pos);

      // Get the selected landmarks.
      final landmarks = _mapController.cursorSelectionLandmarks();

      // Reset the cursor position back to middle of the screen
      await _mapController.resetMapSelection();

      // Check if there is a selected Landmark.
      if (landmarks.isEmpty) {
        return;
      }

      // Highlight the selected landmark.
      _mapController.activateHighlight(landmarks);

      final lmk = landmarks[0];
      setState(() {
        _focusedLandmark = lmk;
      });

      // Use the map controller to center on coordinates.
      _mapController.centerOnCoordinates(
        lmk.coordinates,
        zoomLevel: 70,
        screenPosition: Point<int>(
          _mapController.viewport.width ~/ 2,
          _mapController.viewport.height ~/ 3,
        ),
      );
    });
  }

  void _onCancelLandmarkPanelTap() {
    // Remove landmark highlights from the map.
    _mapController.deactivateAllHighlights();
    _mapController.preferences.routes.clear();

    setState(() {
      _focusedLandmark = null;
    });
  }
}

```

### Range Calculation and Preferences[​](#range-calculation-and-preferences "Direct link to 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 "Direct link to Define State Variables")

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

ranges\_panel.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/range_finder/lib/ranges_panel.dart?ref_type=heads#L32)

```dart
class _RangesPanelState extends State<RangesPanel> {
  int _rangeValue = 3600;
  RouteTransportMode _transportMode = RouteTransportMode.car;

  RouteType _routeType = RouteType.fastest;
  bool _avoidMotorways = false;
  bool _avoidTollRoads = false;
  bool _avoidFerries = false;
  bool _avoidUnpavedRoads = false;

  BikeProfile _bikeProfile = BikeProfile.city;
  double _hillsValue = 0;


```

### Calculate Route Ranges[​](#calculate-route-ranges "Direct link to Calculate Route Ranges")

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

ranges\_panel.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/range_finder/lib/ranges_panel.dart?ref_type=heads#L347)

```dart
  void _onAddRouteRangeButtonPressed(BuildContext context) {
    if (!_doesRouteRangeExist()) {
      _showSnackBar(context, message: "The route is being calculated.");

      // Calling the calculateRoute SDK method.
      // (err, results) - is a callback function that gets called when the route computing is finished.
      // err is an error enum, results is a list of routes.
      RoutingService.calculateRoute([widget.landmark], _getRoutePreferences(), (
        err,
        routes,
      ) {
        ScaffoldMessenger.of(context).clearSnackBars();

        // If there aren't any errors, we display the range.
        if (err == GemError.success) {
          // Get the routes collection from map preferences.
          final routesMap = widget.mapController.preferences.routes;

          // Color the range in a random color.
          final randomColor = Color.fromARGB(
            128,
            Random().nextInt(200),
            Random().nextInt(200),
            Random().nextInt(200),
          );
          RouteRenderSettings settings = RouteRenderSettings(
            fillColor: randomColor,
          );

          // Display the range on map.
          routesMap.add(routes.first, true, routeRenderSettings: settings);

          // Center the camera on range.
          _centerOnRouteRange(routes.first);

          setState(() {
            _addNewRouteRange(routes.first, randomColor);
          });
        }
      });

      setState(() {});
    }
  }

```

![Calculated range within an one hour bike ride](/docs/flutter/assets/images/example_flutter_range_finder3-0d4a4de2fa1105d5906b482eb42dc29b.png)

**Calculated range within an one hour bike ride**

![Calculated range within 20 minute bike ride](/docs/flutter/assets/images/example_flutter_range_finder4-f2be57103232d877b04cae9acb624f52.png)

**Calculated range within 20 minute bike ride**

### Define Route Preferences[​](#define-route-preferences "Direct link to Define Route Preferences")

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

ranges\_panel.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/range_finder/lib/ranges_panel.dart?ref_type=heads#L299)

```dart
  RoutePreferences _getRoutePreferences() {
    // Get the preferences based on the selected transport mode.
    switch (_transportMode) {
      case RouteTransportMode.car:
        return RoutePreferences(
          avoidMotorways: _avoidMotorways,
          avoidTollRoads: _avoidTollRoads,
          avoidFerries: _avoidFerries,
          avoidUnpavedRoads: _avoidUnpavedRoads,
          transportMode: _transportMode,
          routeType: _routeType,
          routeRanges: [_rangeValue],
        );
      case RouteTransportMode.lorry:
        return RoutePreferences(
          avoidMotorways: _avoidMotorways,
          avoidTollRoads: _avoidTollRoads,
          avoidFerries: _avoidFerries,
          avoidUnpavedRoads: _avoidUnpavedRoads,
          transportMode: _transportMode,
          routeType: _routeType,
          routeRanges: [_rangeValue],
          avoidTraffic: _trafficAvoidance,
        );
      case RouteTransportMode.pedestrian:
        return RoutePreferences(
          avoidFerries: _avoidFerries,
          avoidUnpavedRoads: _avoidUnpavedRoads,
          transportMode: _transportMode,
          routeRanges: [_rangeValue],
        );
      case RouteTransportMode.bicycle:
        return RoutePreferences(
          avoidFerries: _avoidFerries,
          avoidUnpavedRoads: _avoidUnpavedRoads,
          transportMode: _transportMode,
          routeType: _routeType,
          routeRanges: [_rangeValue],
          bikeProfile: BikeProfileElectricBikeProfile(
            profile: _bikeProfile,
            eProfile: ElectricBikeProfile(),
          ),
        );
      default:
        return RoutePreferences();
    }
  }

```

### Handle User Interactions[​](#handle-user-interactions "Direct link to Handle User Interactions")

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

ranges\_panel.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/range_finder/lib/ranges_panel.dart?ref_type=heads#L272)

```dart
  void _deleteRouteRange(int index) {
    // Remove the route range from map.
    widget.mapController.preferences.routes.remove(routeRanges[index].route);
    setState(() {
      routeRanges.removeAt(index);
    });
  }

  void _toggleRouteRange(int index) {
    if (routeRanges[index].isEnabled) {
      // If the route range is enabled, remove it from map.
      widget.mapController.preferences.routes.remove(routeRanges[index].route);
      return;
    } else {
      // If the route range is disabled, display it on map and center on it.
      RouteRenderSettings settings = RouteRenderSettings(
        fillColor: routeRanges[index].color,
      );
      widget.mapController.preferences.routes.add(
        routeRanges[index].route,
        true,
        routeRenderSettings: settings,
      );
      _centerOnRouteRange(routeRanges[index].route);
    }
  }

  String _getRouteRangeValueString() {
    final String valueString = (_routeType == RouteType.fastest)
        ? convertDuration(_rangeValue)
        : (_routeType == RouteType.economic)
        ? convertWh(_rangeValue)
        : convertDistance(_rangeValue);
    return valueString;
  }

  bool _doesRouteRangeExist() {
    bool exists = routeRanges.any(
      (range) =>
          range.transportMode == _transportMode &&
          range.value == _getRouteRangeValueString(),
    );
    return exists;
  }

  void _addNewRouteRange(Route route, Color color) {
    Range newRange = Range(
      route: route,
      color: color,
      transportMode: _transportMode,
      value: _getRouteRangeValueString(),
      isEnabled: true,
    );
    routeRanges.add(newRange);
  }

  void _centerOnRouteRange(Route route) {
    const appbarHeight = 50;
    const padding = 20;

    // Use the map controller to center on route above the panel.
    widget.mapController.centerOnRoute(
      route,
      screenRect: Rectangle<int>(
        0,
        (appbarHeight + padding * MediaQuery.of(context).devicePixelRatio)
            .toInt(),
        (MediaQuery.of(context).size.width *
                MediaQuery.of(context).devicePixelRatio)
            .toInt(),
        ((MediaQuery.of(context).size.height / 2 -
                    appbarHeight -
                    2 * padding * MediaQuery.of(context).devicePixelRatio) *
                MediaQuery.of(context).devicePixelRatio)
            .toInt(),
      ),
    );
  }

  // Show a snackbar indicating that the route calculation is in progress.
  void _showSnackBar(
    BuildContext context, {
    required String message,
    Duration duration = const Duration(hours: 1),
  }) {
    final snackBar = SnackBar(content: Text(message), duration: duration);

    ScaffoldMessenger.of(context).showSnackBar(snackBar);
  }

```

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