Unlock the Power of Geofencing in Flutter Google Maps with Haversine Formula
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.