Unlock the Power of Geofencing in Flutter Google Maps with Haversine Formula

Abdur Rehman
7 min readApr 1, 2023

--

Image Source: Morio

Introduction

Google Maps Geo-fencing is a powerful feature that enables developers to create virtual boundaries around real-world locations and receive notifications when a mobile device enters or exits these boundaries. This technology is commonly used in location-based services, such as ride-sharing apps like Uber, to track the user’s location and provide relevant information or actions.

In this article, we will explore how to use Google Maps Geo-fencing in a Flutter app using the Google Maps package and the Haversine formula for Geo-fencing. We will use circles for the target points and show how to update the marker’s live location and continuously calculate the difference between the source and destination points. We will also demonstrate how to customize the logic for when the source reaches the destination.

The final result will look like this.

Prerequisites

Before we get started, make sure you have the following:

  • A basic knowledge of Flutter development.
  • An IDE for Flutter development, such as Android Studio or Visual Studio Code.

Setting Up the Project

Let’s create a new Flutter project by running the following command:

flutter create geo_fencing_demo

Next, add the following dependencies to your pubspec.yaml file:

dependencies:
flutter:
sdk: flutter
google_maps_flutter: ^2.2.0

This will allow us to use the Google Maps package in our Flutter app.

Implementing Geo-fencing

Creating the Map

First, let’s create a basic Google Map in our Flutter app by adding the following code to the build method of our MyApp widget:

GoogleMap(
initialCameraPosition: CameraPosition(
target: LatLng(currentLocation!.latitude! , currentLocation!.longitude!,),
zoom: 13,
),
onMapCreated: (GoogleMapController controller) {
// Set up the controller, and add circles for geo-fencing
},
),

This creates a new Google Map with an initial camera position set to the User current location. We also set up the onMapCreated callback, which is triggered when the map is ready to be used. In this callback, we will set up the controller and add circles for our geofencing areas.

Setting Up the Map Controller

Next, let’s set up the GoogleMapController and add the circles for our geo-fencing areas. Add the following code to the onMapCreated callback:

/// Googe map controller
GoogleMapController mapController = controller;
/// create a circle around the destination location
Set<Circle> circles = Set.from([
Circle(
circleId: CircleId('geo_fence_1'),
center: LatLng(destinationLocation!.latitude, destinationLocation!.longitude),
radius: 200,
strokeWidth: 2,
strokeColor: Colors.green,
fillColor: Colors.green.withOpacity(0.15),
),
]);
/// call this method inside your initState() to add a circle around your destination
/// You can also add circle directly inside GoogleMap(), I will show later,
mapController.addCircles(circles);

This sets up the GoogleMapController and creates a new set of circles for our geo-fencing areas. We create a new Circle object for each geo-fencing area, with a unique ID, center point, radius, and style properties.

Destination / Target Location

We will provide some static destination location LatLng, I get this location according to my near current location, you can set up this destination location according to your need.

/// destination location LatLng, we also create circle around it for geo_fencing
LatLng? destinationLocation = LatLng(31.48345, 74.2944);

Now let’s add the marker for destination location.

 /// Destination Location marker which will be static
Marker destinationMarker = Marker(
markerId: MarkerId('destinationLocation'),
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueBlue),
position: LatLng(destinationLocation!.latitude, destinationLocation!.longitude),
infoWindow: InfoWindow(title: 'Destination Location', snippet: 'destination location...'),
onTap: () {
print('market tapped');
},
);
print('marker added');
/// markers to the map controller
mapController.markers.add(currentLocaMarker);

Get The User Current Location

Now let’s get the user current location. Current user location is acting like source location, which will be updating on user moving.

First, we’ll need to add the location package to our pubspec.yaml file and run flutter packages get:

dependencies:
flutter:
sdk: flutter
google_maps_flutter: ^2.1.1
location: ^4.3.0

Next, let’s add a new currentLocation variable to our MyApp widget:

LocationData? currentLocation;

We’ll also need to add a Location object and call getCurrentLocation() in the initState() method to initialize the user's location:

Location location = Location();
StreamSubscription<LocationData> _locationSubscription;

@override
void initState() {
// TODO: implement initState
super.initState();
getCurrentLocation();
}

void getCurrentLocation() async{

location.getLocation().then((value){

/// Pass the location value to currentLocation
currentLocation = value;

/// add Markers for current location
/// I will define this method later
addCurrentLocMarker(currentLocation!);
});
}

Updating the Marker’s Live Location

Now, let’s add a Location().onLocationChanged listener to our MyApp widget that updates currentLocation whenever the user's location changes:


void getCurrentLocation() async{
Location location = Location();
location.getLocation().then((value){
currentLocation = value;
/// add Markers for current location
addCurrentLocMarker(currentLocation!);
});

/// Listen for user live location changes
_locationSubscription = location.onLocationChanged.listen((newLoc) {

setState(() {
/// update the currentLocation with new location value
currentLocation = newLoc;
/// We have to also update the markers by passing the new location value
addCurrentLocMarker(newLoc);
});


});
}


@override
void dispose() {
super.dispose();
/// cancel stream subscription, to avoid memory leak
_locationSubscription?.cancel();
}

Note that we are also canceling the subscription to the location stream in the dispose() method to prevent memory leaks.

Add Marker for Current Location

Now we will add the marker for current user location. Below method is used to add the marker for current location and call this method from getCurrentLocation method.

  /// Add Markers to the markers set in controller
addCurrentLocMarker(LocationData locationData){
/// Current Location marker, that will also be updating
Marker currentLocaMarker = Marker(
markerId: MarkerId('currentLocation'),
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueBlue),
position: LatLng(locationData!.latitude!, locationData!.longitude!),
infoWindow: InfoWindow(title: 'Current Location', snippet: 'my location'),
onTap: () {
print('current location tapped');
},
);
}

Wow, you still with us, Now let’s show me the power of Haversine formula. This is the magical formula. you don’t need to use any package for achieving geofencing functionality. You can do so by using this haversine formula.

Calculating the Difference Between Points

Now that we have set up our geo-fencing areas and updated the marker’s live location, let’s calculate the distance between the source and destination points using the Haversine formula.

Add the following helper function to your MyApp widget:

/// Haversine Distance function that will calculate the distance betweent
/// two coordinates and return the shortest distance between these two points in meters
/// player1 will be our source and player2 will be destination LatLng
dynamic haversineDistance(LatLng player1, LatLng player2) {
double lat1 = player1.latitude;
double lon1 = player1.longitude;
double lat2 = player2.latitude;
double lon2 = player2.longitude;

var R = 6371e3; // metres
// var R = 1000;
var phi1 = (lat1 * pi) / 180; // φ, λ in radians
var phi2 = (lat2 * pi) / 180;
var deltaPhi = ((lat2 - lat1) * pi) / 180;
var deltaLambda = ((lon2 - lon1) * pi) / 180;

var a = sin(deltaPhi / 2) * sin(deltaPhi / 2) +
cos(phi1) * cos(phi2) *
sin(deltaLambda / 2) *
sin(deltaLambda / 2);

var c = 2 * atan2(sqrt(a), sqrt(1 - a));

var d = R * c; // in metres


return d;
}

Now let's passing the source and destination LatLng values to haversine formula to calculate the difference between our source LatLng and destination LatLng, But what will be the best place to call this method, should we create timer for this to call after every second. Yes, we can do. But I will call this from location listen stream. Where we get the current user latest location and the destination location is already a static one.

/// we listen for current location changes inside getCurrentLocation()
location.onLocationChanged.listen((newLoc) {
var distanceBetween = haversineDistance(LatLng(newLoc.latitude!, newLoc.longitude!), LatLng(destinationLocation.latitude!, destinationLocation.longitude!));
print('distance between... ${distanceBetween}');

});

Haversine Formula

The Haversine formula is a mathematical formula used to calculate the distance between two points on a sphere, such as the Earth. It takes into account the curvature of the Earth’s surface and provides an accurate estimate of the distance between two points, even over long distances.

In programming, the Haversine formula is often used to calculate distances between two points with known latitude and longitude values. This can be useful in a variety of applications, such as GPS tracking, location-based services, and navigation.

Difference Between Target Circle Radius & haversine return number

when we got return value from haversine formula which will be in meter, we have to compare it with our circle radius value if it is less than radius value then it means that user reached to the destination location successfully, Like Uber driver arrive to our location, and we got notifications that our driver arrived to our location. I simply show the flutter toast message, you can customize it according to your need like showing notifications etc.

  location.onLocationChanged.listen((newLoc) {

setState(() {
currentLocation = newLoc;
/// updating the camera location on user move
addCurrentLocMarker(newLoc);
});

location.onLocationChanged.listen((newLoc) {
var distanceBetween = haversineDistance(LatLng(newLoc.latitude!, newLoc.longitude!), LatLng(destinationLocation.latitude!, destinationLocation.longitude!));
print('distance between... ${distanceBetween}');
if(distanceBetween < circleRadius){
print('user reached to the destination...');

///TODO: Put your notification logic here

/// I simply show a snackBar message here you can implement your custom logic here.
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('You reached to the location',
style: TextStyle(color: Colors.white),),
backgroundColor: Colors.redAccent,)
);

}

});

That’s it, you are ready to run your project and test it.
Note: to use live location feature you can use lockito app, which provide you fake location movement.

❤ ❤ Thanks for reading this article ❤❤

If I got something wrong, Let me know in the comments. I would love to improve.

Clap 👏 If this article helps you.

--

--

Abdur Rehman

Software Engineer || Flutter Developer || Blogger || Passionate storyteller || Bibliophile