Overview
flutter_map_markers is a plugin for flutter_map package that provides a flexible canvas based marker layer. It enables you to render interactive markers directly on the map using Canvas.
This is the initial release. There might be bugs or missing features. Use with caution in production. Please report any issues or feature requests you have and give feedback on your experience.
If you find this useful, consider supporting me.
Features
- Interactive markers - Support for tap
- Custom hit area - Define custom hit areas for complex marker shapes
- Marker rotation - Counter-rotate markers to maintain orientation during map rotation
- Culling - Automatically cull markers outside the visible viewport
- Preset shapes and markers - Built in marker presets for common marker types
- Flexible rendering - Full control over marker appearance using Canvas API
- Zoom level Awareness - Change marker graphics based on current zoom level
Getting Started
Installation
Add flutter_map_markers to your pubspec.yaml:
dependencies:
flutter_map_markers: ^0.1.1
flutter_map: add latest version
latlong2: add latest version
Then run:
flutter pub get
Import
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_markers/flutter_map_markers.dart';
import 'package:latlong2/latlong.dart';
Usage
Basic Example
Create a simple map with canvas markers:
FlutterMap(
options: MapOptions(
initialCenter: LatLng(51.5074, -0.1278),
initialZoom: 10,
),
children: [
TileLayer(
urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'your.package.name',
),
CanvasMarkerLayer(
markers: [
CanvasMarker(
position: LatLng(51.5074, -0.1278),
painter: (canvas, center, metersToPixels, latLngToPixelOffset, zoomLevel) {
final paint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
canvas.drawCircle(center, 10, paint);
},
),
],
),
],
)
Using MarkerPresets
There are several built-in marker presets to help you get started quickly.
You can chose to either to use the full marker preset or just the path for more customization. Or you can use it as an example how to construct your own.
Markers
Marker presets are available in the MarkerPresets class for common marker shapes and parameters.
-
MarkerPresets.raindropMarker()a standard raindrop shaped marker.MarkerPresets.textMarker()places a text marker with customizable text.MarkerPresets.iconMarker()places an icon on the map. Defaults to Icons.location_pin as a marker pin.
MarkerPresets class:
final marker = MarkerPresets.raindropMarker(
position: position,
radius: 12,
);
Paths
You can use path from MarkerPresets if you want the shape but more customization.
-
MarkerPresets.ballMarkerPath()A path as a ball with knob pointing to location. Returns (path, center) where center is a center of the ball.MarkerPresets.raindropMarkerPath()More curved path for a raindrop shape. Returns (path, center) where center is the center of the circular part of the raindrop.
To use a preset path simply call a static method from the MarkerPresets class inside your painter function:
You can then use centerPosition to draw additional content centered on the ball.
CanvasMarker(
position: LatLng(51.5074, -0.1278),
painter: (canvas, center, metersToPixels, latLngToPixelOffset, zoomLevel) {
final (path, markerCenterPosition) = MarkerPresets.ballMarkerPath(
center,
ballRadius: 15,
);
final paint = Paint()
..color = Colors.red
..style = PaintingStyle.fill;
canvas.drawPath(path, paint);
canvas.drawCircle(markerCenterPosition, 5, Paint()..color = Colors.white);
},
)
Size
The size function allows you to define the bounding rectangle of the marker for hit testing and culling purposes.
If not provided, then culling will happen based on the single point position of the marker and markers might disappear before fully leaving the screen.
It is recommended to provide accurate bounds for better performance and interaction.
It is used for hit testing if no custom hitArea is provided.
CanvasMarker(
position: LatLng(51.5074, -0.1278),
size: (center, metersToPixels, latLngToPixelOffset, zoomLevel) {
// Define a bounding box of 20x20 pixels around the center
// If you use metersToPixels or latLngToPixelOffset when painting marker, then use the same here to get correct size.
return Rect.fromCenter(center: center, width: 20, height: 20);
},
painter: (canvas, center, metersToPixels, latLngToPixelOffset, zoomLevel) {
final paint = Paint()..color = Colors.green;
canvas.drawCircle(center, 10, paint);
},
)
Interactive Markers
Currently only onTap is supported, but more gesture callbacks will be added in future releases.
Provide hitArea that returns Path to define precise hit areas for markers.
If no hitArea is provided, the bounding rectangle returned by size function will be used.
If both, hitArea and size are not provided, the marker will not be interactive.
See size for more details.
Custom Hit Detection
You can define precise hit areas for complex marker shapes.
Provide a hitArea function that returns a Path representing the clickable area of the marker.
CanvasMarker(
position: LatLng(51.5074, -0.1278),
painter: (canvas, center, metersToPixels, latLngToPixelOffset, zoomLevel) {
final (path, _) = MarkerPresets.ballMarkerPath(center, ballRadius: 15);
final paint = Paint()..color = Colors.purple;
canvas.drawPath(path, paint);
},
hitArea: (center, metersToPixels, latLngToPixelOffset, zoomLevel) {
final (path, _) = MarkerPresets.ballMarkerPath(center, ballRadius: 15);
return path;
},
onTap: () {
print('Accurate hit detection!');
},
)
Advanced: Drawing with Icons and Text
You can use TextPainter to draw icons or text on your markers.
If using TextPainter, make sure to call layout() outside painting for better performance.
Overall don't create new objects unnecessarily inside the painter function to avoid unnecessary allocations during rendering.
final textPainter = TextPainter(
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
);
final icon = Icons.place;
textPainter.text = TextSpan(
text: String.fromCharCode(icon.codePoint),
style: TextStyle(
fontSize: 20,
fontFamily: icon.fontFamily,
color: Colors.white,
),
);
textPainter.layout();
final bgPaint = Paint()..color = Colors.orange;
CanvasMarker(
position: LatLng(51.5074, -0.1278),
painter: (canvas, center, metersToPixels, latLngToPixelOffset, zoomLevel) {
// Draw marker background
final (path, iconCenter) = MarkerPresets.ballMarkerPath(center, ballRadius: 15);
canvas.drawPath(path, bgPaint);
// Draw icon
final iconOffset = iconCenter - Offset(
textPainter.width / 2,
textPainter.height / 2,
);
textPainter.paint(canvas, iconOffset);
},
)
Layer Configuration
Configure the CanvasMarkerLayer with various options:
Showing debug rectangles and hit areas can help during development and troubleshooting size inconsistencies.
CanvasMarkerLayer(
markers: myMarkers,
showDebugRect: false, // Show size debug rectangles around markers
showDebugHitArea: false, // Show debug hit areas
drawHitMarkerLast: true, // Draw tapped marker on top
cullMarkers: true, // Cull off-screen markers
)
API Reference
CanvasMarker
| Property | Type | Description |
|---|---|---|
position |
LatLng |
Geographic position of the marker |
size |
MarkerSize? |
Define the size of the marker |
painter |
CanvasPainter |
Function to draw the marker on canvas |
hitArea |
HitArea? |
Custom hit detection path |
rotate |
bool |
Counter-rotate marker with map rotation |
onTap |
Function? |
Callback for tap events |
CanvasMarkerLayer
| Property | Type | Description |
|---|---|---|
markers |
List<CanvasMarker> |
List of markers to render |
showDebugRect |
bool |
Display debug rectangles (default: false) |
showDebugHitArea |
bool |
Display debug hit areas (default: false) |
drawHitMarkerLast |
bool |
Draw hit marker on top (default: false) |
cullMarkers |
bool |
Enable marker culling (default: true) |
Painter Function Signature
Rect Function(
Canvas canvas,
Offset center,
double Function(double meters, LatLng? position) metersToPixels,
Offset Function(LatLng latLng, {LatLng? referencePoint}) latLngToPixelOffset,
int zoomLevel,
)
Parameters:
canvas- The canvas to draw oncenter- The marker's center position in pixelsmetersToPixels- Convert meters to pixels at current zoom and positionlatLngToPixelOffset- Convert lat/lng coordinates to pixel offsetszoomLevel- Current map zoom level
Example App
Check out the complete example in the example/ folder, which demonstrates: