Flutter Map Markers

Flutter Map Markers example screenshot Flutter Map Markers example screenshot

Canvas based marker rendering for flutter_map


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.

To use a preset marker simply call a static method from the 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 on
  • center - The marker's center position in pixels
  • metersToPixels - Convert meters to pixels at current zoom and position
  • latLngToPixelOffset - Convert lat/lng coordinates to pixel offsets
  • zoomLevel - Current map zoom level

Example App

Check out the complete example in the example/ folder, which demonstrates: