google_map_utils 0.0.3 copy "google_map_utils: ^0.0.3" to clipboard
google_map_utils: ^0.0.3 copied to clipboard

Drawing, editing & measurement widgets for Google Maps Flutter. Built on map_utils_core algorithms. Interactive polygon/polyline/circle drawing, vertex editing, snapping, undo/redo, GeoJSON import/export.

google_map_utils #

A drawing, editing & measurement toolkit for google_maps_flutter. Built on map_utils_core algorithms.

pub package likes pub points


What is this? #

google_map_utils is an addon for google_maps_flutter — it does not replace it. Drop it into any existing Google Maps project and instantly unlock interactive drawing, shape editing, snapping, measurement, and GeoJSON support.

Same drawing state, same algorithms, same GeoJSON — just a different map engine. If you need the flutter_map binding instead, see flutter_map_utils.


Features #

Category Capabilities
Drawing Polygon, polyline, rectangle, circle, freehand — tap or drag to draw
Editing Drag vertices, insert midpoints, delete vertices (long-press), drag whole shapes
Hole Cutting Draw holes inside existing polygons
Selection Tap-to-select with hit-testing on edges and fills
Snapping Vertex, midpoint, edge, intersection, grid, perpendicular — priority-based
Measurement Distance & area with metric/imperial labels, per-segment display
Undo / Redo Full command-pattern history with configurable depth
GeoJSON Import & export with round-trip fidelity (string or Map)
Geometry Point-in-polygon, centroid, area, length, bounding box, self-intersection detection
Styles Fill, stroke, opacity, selected/hover states, presets, JSON serialization
UI Widgets Toolbar, info panel — or build your own
Cross-platform Android, iOS, Web

Installation #

Add to your pubspec.yaml:

dependencies:
  google_map_utils: ^0.0.2

Then run:

flutter pub get

Note: This package depends on google_maps_flutter ^2.10.0. It is pulled in automatically.

This package re-exports everything from map_utils_core — including LatLng and all geo types — so you don't need to add any coordinate package separately.

Platform setup: You still need a Google Maps API key configured per the google_maps_flutter setup guide.


Quick Start #

Option A: All-in-one widget #

The fastest way to get a full drawing editor on screen:

import 'package:google_map_utils/google_map_utils.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

final drawingState = DrawingState();

GoogleMapGeometryEditor(
  drawingState: drawingState,
  initialCameraPosition: CameraPosition(
    target: LatLng(51.5, -0.1),
    zoom: 12,
  ),
)

This gives you a Google Map with a toolbar, drawing layer, selection, editing handles, info panel, and measurement — all wired together.

Option B: Manual composition #

For full control, compose the pieces yourself inside a Stack:

final drawingState = DrawingState();
final controller = GmDrawingController(drawingState: drawingState);
final renderer = GmShapeRenderer(
  drawingState: drawingState,
  onShapeTap: (id) => drawingState.selectShape(id),
);

Stack(
  children: [
    GoogleMap(
      initialCameraPosition: CameraPosition(
        target: LatLng(51.5, -0.1),
        zoom: 12,
      ),
      onMapCreated: controller.onMapCreated,
      onTap: controller.handleTap,
      onLongPress: controller.handleLongPress,
      onCameraMove: (_) => controller.onCameraChanged(),
      polygons: renderer.polygons,
      polylines: renderer.polylines,
      circles: renderer.circles,
      scrollGesturesEnabled: !drawingState.shouldAbsorbMapGestures,
      zoomGesturesEnabled: !drawingState.shouldAbsorbMapGestures,
      rotateGesturesEnabled: !drawingState.shouldAbsorbMapGestures,
      tiltGesturesEnabled: !drawingState.shouldAbsorbMapGestures,
    ),

    // Freehand drawing (only when in freehand mode)
    if (drawingState.activeMode == DrawingMode.freehand)
      GmFreehandOverlay(controller: controller),

    // Vertex editing handles (only when a shape is selected)
    if (drawingState.selectedShape != null && !drawingState.isDrawing)
      GmVertexOverlay(controller: controller),

    // Whole-shape dragging
    if (drawingState.selectedShape != null && !drawingState.isDrawing)
      GmShapeDragger(controller: controller),

    // Toolbar
    DrawingToolbar(drawingState: drawingState),
  ],
)

Drawing Shapes #

Polygon #

drawingState.setMode(DrawingMode.polygon);
// User taps on the map to add vertices.
// Long-press to finish the polygon.
// Or call: controller.finishDrawing();

Polyline #

drawingState.setMode(DrawingMode.polyline);
// Each tap adds a point. Long-press to finish.

Rectangle #

drawingState.setMode(DrawingMode.rectangle);
// First tap = corner A, second tap = corner B → auto-finishes.

Circle #

drawingState.setMode(DrawingMode.circle);
// First tap = center, second tap = radius → auto-finishes.

Freehand #

drawingState.setMode(DrawingMode.freehand);
// The GmFreehandOverlay captures pointer drag → collects screen coordinates
// → batch-converts to LatLng on pointer-up → simplifies → smooths → commits.

Editing Shapes #

drawingState.setMode(DrawingMode.select);

// Tap a shape to select it.
// GmVertexOverlay renders draggable handles at every vertex.
// Drag a vertex to move it.
// Tap an edge midpoint to insert a new vertex.
// Long-press a vertex to delete it (min vertex count enforced).
// GmShapeDragger lets you drag the whole shape.

Cutting Holes #

// First, select a polygon:
drawingState.selectShape(polygonId);
drawingState.setMode(DrawingMode.hole);
drawingState.selectShape(polygonId); // re-select after mode change

// Each tap adds a hole vertex. Long-press to finish.
// The hole is cut from the selected polygon.

Measurement #

final measureState = GmMeasurementState();

// In your GoogleMapGeometryEditor:
GoogleMapGeometryEditor(
  drawingState: drawingState,
  measurementState: measureState,
  initialCameraPosition: ...,
)

// Or add manually to your Stack:
// 1. Add the measurement polyline:
polylines.addAll(GmMeasurementOverlay.buildMeasurementPolyline(measureState));
// 2. Add the label overlay:
GmMeasurementOverlay(
  measurementState: measureState,
  controller: controller,
  unit: GmMeasurementUnit.metric, // or .imperial
)

// Tap on the map in measure mode to add points:
drawingState.setMode(DrawingMode.measure);

// Read values:
print(measureState.totalDistanceMeters); // 1234.5
print(measureState.areaSquareMeters);    // 56789.0
print(measureState.segmentDistances);    // [500.2, 734.3]

GeoJSON Export / Import #

// Export all shapes to GeoJSON string
final geojson = GeoJsonUtils.toGeoJsonString(drawingState.shapes);

// Export to Map
final map = GeoJsonUtils.toFeatureCollection(drawingState.shapes);

// Import from GeoJSON string
final shapes = GeoJsonUtils.fromGeoJsonString(geojsonString);
drawingState.loadShapesFromJson(shapes.map((s) => s.toJson()).toList());

Snapping #

final snapEngine = SnappingEngine(
  config: SnapConfig(
    tolerancePixels: 15,
    priorities: [SnapType.vertex, SnapType.midpoint, SnapType.grid],
    gridSpacing: 0.001,
  ),
);

final result = snapEngine.snap(
  candidatePoint: someLatLng,
  shapes: drawingState.shapes,
);

if (result != null) {
  // Use result.point — the snapped coordinate
  // result.type — which snap type matched
}

Shape Styles #

// Custom style
final style = ShapeStyle(
  fillColor: Color(0x5500FF00),
  borderColor: Color(0xFF00FF00),
  borderWidth: 3.0,
  fillOpacity: 0.3,
  strokeType: StrokeType.dashed,
  selectedOverride: ShapeStyle(
    borderColor: Color(0xFFFF0000),
    borderWidth: 4.0,
  ),
);

drawingState.defaultStyle = style;

// Or use presets
drawingState.defaultStyle = ShapeStylePresets.zone;     // blue fill
drawingState.defaultStyle = ShapeStylePresets.warning;   // red fill
drawingState.defaultStyle = ShapeStylePresets.route;     // line-only

Undo / Redo #

drawingState.undo();
drawingState.redo();

print(drawingState.canUndo); // true
print(drawingState.canRedo); // false

// History depth is configurable:
final state = DrawingState(maxHistoryDepth: 50);

Serialization #

// Save shapes to JSON
final json = drawingState.shapesToJson();

// Restore shapes from JSON
drawingState.loadShapesFromJson(json);

// Clear everything
drawingState.clearAll();

Architecture #

┌──────────────────────────────────────────┐
│              Stack                        │
│  ┌────────────────────────────────────┐  │
│  │           GoogleMap(               │  │
│  │             polygons: renderer.*   │  │  ← native Google Maps shapes
│  │             polylines: renderer.*  │  │
│  │             circles: renderer.*    │  │
│  │           )                        │  │
│  ├────────────────────────────────────┤  │
│  │       GmFreehandOverlay            │  │  ← freehand drawing preview
│  ├────────────────────────────────────┤  │
│  │        GmVertexOverlay             │  │  ← vertex/edge handles
│  ├────────────────────────────────────┤  │
│  │        GmShapeDragger              │  │  ← whole-shape drag
│  ├────────────────────────────────────┤  │
│  │     GmMeasurementOverlay           │  │  ← measurement labels
│  ├────────────────────────────────────┤  │
│  │     DrawingToolbar + ShapeInfoPanel│  │  ← shared UI widgets
│  └────────────────────────────────────┘  │
└──────────────────────────────────────────┘
         │
    DrawingState (ChangeNotifier)
         │
    UndoRedoManager ← command pattern history

State management: DrawingState is a plain ChangeNotifier. Use it with Provider, Riverpod, Bloc, ListenableBuilder — whatever you prefer.

Coordinate conversion: Google Maps uses async coordinate conversion (getLatLng / getScreenCoordinate). All overlays handle this internally — you don't need to think about it.


API Reference #

Core (re-exported from map_utils_core) #

Class Purpose
DrawingState Central state: shapes, selection, mode, undo/redo
DrawingMode Enum: none, polygon, polyline, rectangle, circle, freehand, select, measure, hole
DrawableShape Sealed base class — DrawablePolygon, DrawablePolyline, DrawableCircle, DrawableRectangle
ShapeStyle Fill color, stroke color, stroke width, opacity, selected/hover states, JSON serializable
UndoRedoManager Command-pattern history with AddShapeCommand, RemoveShapeCommand, UpdateShapeCommand
GeometryUtils pointInPolygon, centroid, polygonArea, polylineLength, simplifyPath, smoothPath, and more
GeoJsonUtils toGeoJson, fromGeoJson, toGeoJsonString, fromGeoJsonString, toFeatureCollection
SnappingEngine Priority-based snapping: vertex, midpoint, edge, intersection, grid, perpendicular
SelectionUtils findClosestShape() with configurable tolerance

Google Maps Widgets #

Widget / Class Purpose
GoogleMapGeometryEditor All-in-one wrapper that composes everything below
GmShapeRenderer Converts DrawableShape list → Set<Polygon>, Set<Polyline>, Set<Circle>
GmDrawingController Tap routing, coordinate conversion, mode dispatch, circle preview
GmFreehandOverlay Listener-based freehand with live CustomPaint preview
GmVertexOverlay Positioned vertex/edge handles — drag, insert, delete
GmShapeDragger Whole-shape drag via async coordinate conversion
GmMeasurementOverlay Per-segment distance labels + total distance + area
GmMeasurementState Pure ChangeNotifier for measurement points and calculations

Shared UI Widgets (from core) #

Widget Purpose
DrawingToolbar Mode selector with undo/redo/delete buttons — fully customizable via buttonBuilder
ShapeInfoPanel Displays selected shape info: type, area, perimeter, coordinates

Extensions #

Extension Purpose
StrokeTypeToGmPattern Converts StrokeTypeList<PatternItem> for polylines
LatLngToGm Converts LatLng (from map_utils_core) → google_maps.LatLng
GmLatLngToCore Converts google_maps.LatLngLatLng (from map_utils_core)
LatLngListToGm Converts List<LatLng>List<google_maps.LatLng>
GmGeometryUtils boundingBox()LatLngBounds from a list of points

Supported Shape Types #

Shape Drawing Editing GeoJSON Holes
Polygon
Polyline
Rectangle
Circle
Freehand ✅ (as polyline)

Compatibility #

Dependency Version
Flutter >= 3.27.0
Dart >= 3.6.0
google_maps_flutter ^2.10.0
map_utils_core ^0.0.2

Switching Between Map Engines #

flutter_map_utils and google_map_utils are not interchangeable — they bind to different map SDKs with different APIs. Choose based on your map engine:

You use Package to add
flutter_map flutter_map_utils
google_maps_flutter google_map_utils

Both packages share the same DrawingState, ShapeStyle, GeoJsonUtils, snapping, serialization, and undo/redo logic from map_utils_core. So if you ever need to swap map engines, the migration is minimal:

  1. Replace flutter_map_utils with google_map_utils in your pubspec (or vice versa)
  2. Replace FlutterMapGeometryEditor with GoogleMapGeometryEditor (or vice versa)
  3. Keep all DrawingState, ShapeStyle, GeoJsonUtils, and other core code unchanged

Your shapes, styles, GeoJSON exports, and state management stay identical.


About #

Built and maintained by UBXTY Unboxing Technology.

Part of the flutter_map_utils monorepo.


License #

MIT — see LICENSE for details.

0
likes
130
points
82
downloads

Documentation

API reference

Publisher

verified publisherubxty.com

Weekly Downloads

Drawing, editing & measurement widgets for Google Maps Flutter. Built on map_utils_core algorithms. Interactive polygon/polyline/circle drawing, vertex editing, snapping, undo/redo, GeoJSON import/export.

Homepage
Repository (GitHub)
View/report issues

Topics

#google-maps #map #gis #drawing #geometry

License

MIT (license)

Dependencies

flutter, google_maps_flutter, latlong2, map_utils_core

More

Packages that depend on google_map_utils