Mastering Flutter Map: A Practical Guide?-?Part?1
Imad Eddarraz
Mobile Engineer | Flutter ?? | Creating Seamless User Experience??| GenAI Enthusiast
Hello everyone,
This is not just an introduction to this article but also to a new series of articles on how to build a production app that requires maps. Let’s keep things concise, useful, and clear by asking questions and answering them directly. This will make the process easier to follow.
What is Flutter?Map?
The answer is straightforward, and I’ll quote directly from the MapStadia documentation:
“Flutter Map is a Flutter-first map library built from the ground up (not a wrapper for an existing native library) and boasts broad cross-platform compatibility due to its design. It is loosely inspired by Leaflet, a JavaScript raster map library, and features a wide range of plugins.”
Additionally, the Flutter Map documentation states:
“A versatile mapping package for Flutter. Simple and easy to learn, yet completely customizable and configurable, it’s the best choice for mapping in your Flutter app.”
Sources:
How Does It?Work?
Let’s dive right in and learn as we go.
Showing Our Flutter?Map
To achieve this, follow these steps:
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
class MapPage extends StatefulWidget {
const MapPage({super.key});
@override
State<MapPage> createState() => _MapPageState();
}
class _MapPageState extends State<MapPage> {
final MapController mapController = MapController();
@override
Widget build(BuildContext context) {
return FlutterMap(
mapController: mapController,
options: MapOptions(
initialCenter: const LatLng(33.583760, -7.648748),
initialZoom: 15,
),
children: [
TileLayer(
urlTemplate:
'https://{s}.google.com/vt/lyrs=m,h&x={x}&y={y}&z={z}&hl=ar-MA&gl=MA',
subdomains: const ['mt0', 'mt1', 'mt2', 'mt3'],
userAgentPackageName: 'com.example.app',
),
const RichAttributionWidget(
attributions: [
TextSourceAttribution(
'OpenStreetMap contributors',
// onTap: () =>
// launchUrl(Uri.parse('https://openstreetmap.org/copyright')),
),
],
),
],
);
}
}
Explaining the?Code
Note: I encourage everyone, especially Moroccan developers, to use URLs that accurately represent our territory. I’ve noticed that some applications using OpenStreetMap have incorrect tile URLs.
Moving Forward: MarkerLayer and?Marker
Imagine you have a restaurant and want to highlight it on the map like this:
Markers are perfect for this. Here’s how you can create one:
const MarkerLayer(
markers: [
Marker(
point: LatLng(33.583760, -7.648748),
width: 80,
height: 80,
child: CircleAvatar(
backgroundColor: Colors.white,
child: CircleAvatar(
radius: 35.0,
backgroundImage: AssetImage("assets/images/restaurent.jpg"),
),
),
),
],
),
Adding a Pop-Up to the?Marker
What if you want to show a pop-up when the marker is clicked, like this?
While there’s no direct parameter in the marker widget for this, Flutter Map has a rich ecosystem of plugins. For this functionality, we’ll use the flutter_map_marker_popup package.
final List<String> restaurentPresentations = [
"? We guarantee that you'll experience the traditional Moroccan vibe, from the beautiful lighting to the amazing cuisine. The environment also offers plenty to enhance your overall experience.",
"?? Imagine enjoying your favorite meal in a serene place facing the sea, with the sky stretching endlessly, free from any barriers or limitations. You're welcome!",
"?? Are you looking for a calm place with a green environment and a great chef who can cook the best meal for you? If so, we are waiting for you!",
];
// List of markers
static final List<LatLng> markerPositions = [
const LatLng(33.592638, -7.643341),
const LatLng(33.594438, -7.655744),
const LatLng(33.583760, -7.648748),
];
// List of images
static final List<String> images = [
"assets/images/restaurent.jpg",
"assets/images/restaurent_2.jpg",
"assets/images/restaurent_3.jpg",
];
// Mapping marker positions to Marker widgets with corresponding images
static final List<Marker> markers =
List.generate(markerPositions.length, (index) {
return Marker(
point: markerPositions[index],
width: 80,
height: 80,
child: CircleAvatar(
backgroundColor: Colors.white,
child: CircleAvatar(
radius: 35.0,
backgroundImage: AssetImage(
images[index],
),
),
),
);
});
Explaining the?Code
Here, I created a list of image URLs and a list of markers. Then, I mapped the markers and assigned each one an image. Next, I used the PopupMarkerLayer inside the children parameter:
PopupMarkerLayer(
options: PopupMarkerLayerOptions(
markers: markers,
popupDisplayOptions: PopupDisplayOptions(
builder: (BuildContext context, Marker marker) => SizedBox(
width: 300,
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
restaurentPresentations[markers.indexOf(marker)],
),
),
),
),
),
),
),
Showing a Bottom Sheet on Marker?Tap
Another common functionality is showing a bottom sheet when a marker is tapped, like this:
This is similar to showing a pop-up but offers more options. You can achieve this by modifying the marker widget to display a bottom sheet (a built-in Flutter widget) or by using a package like sliding_up_panel.
To implement this using the first approach, update your markers code and call it in the MarkerLayer:
final List<Marker> markers = List.generate(markerPositions.length, (index) {
return Marker(
point: markerPositions[index],
width: 80,
height: 80,
child: GestureDetector(
onTap: () => showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Text(restaurentPresentations[index]),
);
},
),
child: CircleAvatar(
backgroundColor: Colors.white,
child: CircleAvatar(
radius: 35.0,
backgroundImage: AssetImage(images[index]),
),
),
),
);
});
Note: I used GestureDetector instead of InkWell because InkWell requires a Material widget, which could complicate things. GestureDetector works perfectly for this use case. I also used showModalBottomSheet to display a modal bottom sheet with more information about the restaurant.
In your children, add the MarkerLayer and pass the markers like this:
MarkerLayer(
markers: markers,
),
Polygons: Building a Construction Map
Have you ever seen a construction site with a plot of land divided into sections, each with a specific color and labeled for future construction (e.g., school, gym, supermarket)? Each section is a polygon. Let’s build one and then create a construction app to show future projects.
To build a polygon, use the PolygonLayer and pass a list of polygons to it:
领英推荐
PolygonLayer(
polygons: [
Polygon(
points: [
const LatLng(33.595593, -7.655844),
const LatLng(33.595081, -7.641224),
const LatLng(33.590756, -7.641067),
const LatLng(33.591054, -7.655844),
],
color: Colors.blue.withOpacity(0.7),
label: "Big school",
labelStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24),
),
],
),
You can add multiple polygons to the PolygonLayer to show different sections of the construction site:
PolygonLayer(
polygons: [
Polygon(
points: [
const LatLng(33.595593, -7.655844),
const LatLng(33.595081, -7.641224),
const LatLng(33.590756, -7.641067),
const LatLng(33.591054, -7.655844),
],
color: Colors.blue.withOpacity(0.7),
label: "University",
labelStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24),
),
Polygon(
points: [
const LatLng(33.590922, -7.646117),
const LatLng(33.590756, -7.641067),
const LatLng(33.586216, -7.641539),
const LatLng(33.587026, -7.646474),
],
color: Colors.green.withOpacity(0.7),
label: "Mall",
labelStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
Polygon(
points: [
const LatLng(33.591054, -7.655844),
const LatLng(33.59097, -7.650136),
const LatLng(33.587074, -7.649822),
const LatLng(33.587408, -7.655701),
],
color: Colors.red.withOpacity(0.7),
label: "Sports Club",
labelStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
],
)
Note: If the label doesn’t appear, adjust the text size to fit within the polygon.
Adding Interactivity to?Polygons
Let’s take it a step further. Imagine a real estate company wants to display future construction plans on the map, and when someone taps on a specific area (a polygon), they can get more information. How would you achieve this?
Here’s the solution:
Here’s where the fun begins: How do you detect if a point lies inside a polygon?
This problem is known as Point in Polygon (PIP), and one solution is the Ray Casting Algorithm. This algorithm is widely used in fields like computer vision, geographic information systems (GIS), and computer-aided design (CAD).
To learn more about the Ray Casting Algorithm, check out these resources:
Applying the Ray Casting Algorithm in?Flutter
Fortunately, the Flutter community has already solved this problem with the point_in_polygon package by using Ray Casting Algorithm. Here’s how to use it:
// list of polygon points
final List<LatLng> polygonPoints = [
const LatLng(33.595593, -7.655844),
const LatLng(33.595081, -7.641224),
const LatLng(33.590756, -7.641067),
const LatLng(33.591054, -7.655844),
];
@override
Widget build(BuildContext context) {
// ... rest of your build method
}
PolygonLayer(
polygons: [
Polygon(
points: polygonPoints,
color: Colors.blue.withOpacity(0.7),
label: "University",
labelStyle: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 24,
),
),
],
)
@override
Widget build(BuildContext context) {
return FlutterMap(
mapController: mapController,
options: MapOptions(
initialCenter: const LatLng(33.583760, -7.648748),
initialZoom: 15,
onTap: ((tapPosition, point) {
debugPrint("This is the point with coordinates (x, y): $point");
Point tapedPoint = Point(y: point.latitude, x: point.longitude);
final List<Point> polygonPoints = [
Point(y: 33.595593, x: -7.655844),
Point(y: 33.595081, x: -7.641224),
Point(y: 33.590756, x: -7.641067),
Point(y: 33.591054, x: -7.655844),
];
if (Poly.isPointInPolygon(tapedPoint, polygonPoints)) {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return const Padding(
padding: EdgeInsets.all(16.6),
child: Text(
"This is a large university offering over 30 specialties in web and mobile development, AI, and cybersecurity. It is a public university with various research laboratories, spaces dedicated to applied technology, and areas for robotics competitions. There are also workshops equipped with all types of 3D printers, CNC machines, and automated control systems. We also have a data center and a supercomputer for AI research. You are welcome to join us!",
),
);
},
);
} else {
debugPrint("This point: $point is not inside the polygon.");
}
}),
),
children: [
// ... rest of your children
],
);
}
Explaining the?Code
You should know that this package doesn’t use LatLng for points but instead uses a class called Point. For example, instead of writing const LatLng(33.595593, -7.655844), you will write Point(y: 33.595593, x: -7.655844).
But if the point is not inside the polygon, we’ll get this message with the point:
Polylines: Building a TGV Route?Map
Imagine you’re working on an app for a train company building a TGV in Morocco. You need to show the progress of the construction by drawing polylines between cities (e.g., Tangier to Casablanca).
To draw a polyline, use the PolylineLayer:
First, to see the polyline that will pass through several cities from Tangier to Casablanca, minimize the initial zoom to 8 instead of the previous 15, like this:
@override
Widget build(BuildContext context) {
return FlutterMap(
mapController: mapController,
options: MapOptions(
initialCenter: const LatLng(33.583760, -7.648748),
initialZoom: 8,
),
// ... rest of your FlutterMap properties
);
}
// Draw a polyline
PolylineLayer(
polylines: [
Polyline(
points: const [
LatLng(35.7712654, -5.7865388),
LatLng(35.0044751, -5.9133911),
LatLng(34.2513818, -6.5983018),
LatLng(34.0164539, -6.8382672),
LatLng(33.9173753, -6.9307315),
LatLng(33.5897945, -7.5940499),
],
color: Colors.blue,
strokeWidth: 2.0,
borderStrokeWidth: 4.0,
borderColor: Colors.yellow,
),
],
)
Making Polylines Interactive
To make the polyline tappable, you can use the flutter_map_tappable_polyline package or create a custom solution based on your needs.
Circle Layer: Visualizing Farms
In Saudi Arabia, farms are often circular due to the center pivot irrigation system. If your company wants to build an app to help these farmers, you can represent each farm as a circle on the map.
Use the CircleLayer to achieve this:
CircleLayer(
circles: [
CircleMarker(
point: const LatLng(33.590756, -7.641067),
radius: 1000,
useRadiusInMeter: true,
color: Colors.green.withOpacity(0.5),
borderStrokeWidth: 4.0,
),
],
)
Attribution Layer
The AttributionLayer helps you credit the sources you use, as required by their terms of service. Use the RichAttributionWidget class for this:
RichAttributionWidget(
attributions: [
TextSourceAttribution(
'OpenStreetMap contributors',
onTap: () =>
launchUrl(Uri.parse('https://openstreetmap.org/copyright')),
),
],
),
Note: Don’t forget to use the url_launcher package to handle URLs properly.
In the next part of this series, we’ll explore how to draw routes that users can follow from point A to point B. Stay tuned!
I hope you enjoyed reading and applying this article. Feel free to share your opinions or experiences, and if you have any questions, you're welcome to ask. Thanks for reading, and I hope you apply its content. Stay tuned for the upcoming parts of our Flutter map series.