Flutter — Create Dotted Circular Polygon Shape on Google Map Like Native Platform

Mohit Gupta
Wheelseye Engineering
4 min readJan 11, 2024
Native Output
Flutter Output

Hello everyone,

At wheelseye, we were developing a feature that enables users to designate a specific location on a Google Map along with a predefined radius. The objective is to generate a visually distinctive representation of this alert zone using a dotted circular polygon. By employing the Google Maps we aim to seamlessly integrate this functionality into our application. This will not only enhance the user experience but also provide a clear and intuitive way for users to set and visualize the boundaries of their entry or exit alerts. The resulting implementation will empower users to define and manage alert zones effectively, contributing to a more robust and user-friendly platform.

Wheelseye Operator App

Many of you have likely used Google Maps and worked with Polygon Shapes. Now, we’re going to enhance our map by incorporating a Circular Dotted Border, a feature not provided by the Google Map Widget.

Let’s dive into the implementation

Step 1: Create variable for circle and poly line.

Circle _circle = const Circle(circleId: CircleId('dottedCircle'));
Polyline _polyline = const Polyline(
polylineId: PolylineId('dottedPolyLine'),
);

Step 2: Create Google Map Widget in build method.

GoogleMap(
polylines: {_polyline},
circles: {_circle},
initialCameraPosition: const CameraPosition(
target: LatLng(20.5937, 78.9629),
zoom: 15,
),
)

Step 3: Create new circle and polyline on google map in init state method.

_circle =  Circle(
circleId: const CircleId('dottedCircle'),
center: const LatLng(20.5937, 78.9629),
radius:500,
strokeWidth: 0,
fillColor: Colors.orangeAccent.withOpacity(.25),
);
_polyline = Polyline(
polylineId: const PolylineId('dottedCircle'),
color: Colors.deepOrange,
width: 2,
patterns: [
PatternItem.dash(20),
PatternItem.gap(20),
],
points: List<LatLng>.generate(
360,
(index) => calculateNewCoordinates(
const LatLng(20.5937, 78.9629).latitude,
const LatLng(20.5937, 78.9629).longitude,
500,
double.parse(index.toString()))),
);

Step 4: Main Trick for this Dotted circle lies here, the calculateNewCoordinates function.

LatLng calculateNewCoordinates(
double lat, double lon, double radiusInMeters, double angleInDegrees) {
// Convert angle from degrees to radians
double PI = 3.141592653589793238;

double angleInRadians = angleInDegrees * PI / 180;

// Constants for Earth's radius and degrees per meter
const earthRadiusInMeters = 6371000; // Approximate Earth radius in meters
const degreesPerMeterLatitude = 1 / earthRadiusInMeters * 180 / pi;
final degreesPerMeterLongitude =
1 / (earthRadiusInMeters * cos(lat * PI / 180)) * 180 / pi;

// Calculate the change in latitude and longitude in degrees
double degreesOfLatitude = radiusInMeters * degreesPerMeterLatitude;
double degreesOfLongitude = radiusInMeters * degreesPerMeterLongitude;

// Calculate the new latitude and longitude
double newLat = lat + degreesOfLatitude * sin(angleInRadians);
double newLon = lon + degreesOfLongitude * cos(angleInRadians);
return LatLng(newLat, newLon);
}

Now, let’s proceed with the provided code.

As a bonus, you can achieve the following result:

We’re all set with the code. Feel free to experiment and explore additional features.

Happy coding!
Mohit Gupta
Flutter Developer

Source Code

https://github.com/Rishyash/Flutter-Dotted-Circular-Polygon-Google-Map

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

void main() {
runApp(const MyApp());
}

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

// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(),
);
}
}

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

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

class _MyHomePageState extends State<MyHomePage> {

Circle _circle = const Circle(circleId: CircleId('dottedCircle'));
Polyline _polyline = const Polyline(
polylineId: PolylineId('dottedPolyLine'),
);

@override
void initState() {
super.initState();
_circle = Circle(
circleId: const CircleId('dottedCircle'),
center: const LatLng(20.5937, 78.9629),
radius:500,
strokeWidth: 0,
fillColor: Colors.orangeAccent.withOpacity(.25),
);
_polyline = Polyline(
polylineId: const PolylineId('dottedCircle'),
color: Colors.deepOrange,
width: 2,
patterns: [
PatternItem.dash(20),
PatternItem.gap(20),
],
points: List<LatLng>.generate(
360,
(index) => calculateNewCoordinates(
const LatLng(20.5937, 78.9629).latitude,
const LatLng(20.5937, 78.9629).longitude,
500,
double.parse(index.toString()))),
);
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: GoogleMap(
polylines: {_polyline},
circles: {_circle},
initialCameraPosition: const CameraPosition(
target: LatLng(20.5937, 78.9629),
zoom: 15,
),
),
);
}

LatLng calculateNewCoordinates(
double lat, double lon, double radiusInMeters, double angleInDegrees) {
// Convert angle from degrees to radians
double PI = 3.141592653589793238;

double angleInRadians = angleInDegrees * PI / 180;

// Constants for Earth's radius and degrees per meter
const earthRadiusInMeters = 6371000; // Approximate Earth radius in meters
const degreesPerMeterLatitude = 1 / earthRadiusInMeters * 180 / pi;
final degreesPerMeterLongitude =
1 / (earthRadiusInMeters * cos(lat * PI / 180)) * 180 / pi;

// Calculate the change in latitude and longitude in degrees
double degreesOfLatitude = radiusInMeters * degreesPerMeterLatitude;
double degreesOfLongitude = radiusInMeters * degreesPerMeterLongitude;

// Calculate the new latitude and longitude
double newLat = lat + degreesOfLatitude * sin(angleInRadians);
double newLon = lon + degreesOfLongitude * cos(angleInRadians);
return LatLng(newLat, newLon);
}
}

--

--