maplibre_flutter_gpu 0.2.0
maplibre_flutter_gpu: ^0.2.0 copied to clipboard
MapLibre maps for Flutter, rendered with Flutter GPU.
maplibre_flutter_gpu #
Render MapLibre maps with Flutter GPU without Platform Views. Maps and Flutter Widgets stay smooth and synchronized, with support for custom 3D rendering.
Widgets on the map #
Have you ever wanted to place a Flutter widget on a map as a marker?
With conventional map packages, the widget falls out of sync while the map moves.
Flutter and the map use different rendering engines, so their frames cannot stay perfectly synchronized. The effect becomes more noticeable on lower-end devices and can make the map difficult to use.
MapLibre Flutter GPU solves this by letting Flutter render the map and the widgets together.
Everything (Text and Symbols) is a Widget #
Every label and symbol on the map is a Flutter widget. You can also place any widget you like at any geographic coordinate.
Use Flutter to restyle place names and regional labels or bring them to life with animations. Map content and your interface share the same widget system and stay synchronized while the camera moves.
Place 3D objects in map space #
Because the map is rendered with Flutter GPU, you can render custom geometry directly in the map's 3D coordinate space from Dart.
New to Flutter GPU? That is fine. Modern LLMs can already help implement a surprisingly wide range of Flutter GPU effects.
Turn a flat map pin into a 3D object positioned in geographic space. Animate cars along roads. If you want to go further, you could even build a flight simulator or an FPS game around a real map.
Supported platforms #
MapLibre Flutter GPU currently supports the following platforms.
- iOS 15.0 or later
- Android
- macOS 14.3 or later
- Windows x64 and ARM64
- Linux x64 and ARM64
Web is not supported because Flutter GPU is unavailable there.
Getting started #
Flutter 3.47.0 or later is required.
Add MapLibre Flutter GPU to your project with this command.
flutter pub add maplibre_flutter_gpu
Enable Flutter GPU in your app's ios/Runner/Info.plist and
macos/Runner/Info.plist.
<key>FLTEnableFlutterGPU</key>
<true/>
On Windows and Linux, enable Flutter GPU when running the app.
flutter run -d windows --enable-flutter-gpu
flutter run -d linux --enable-flutter-gpu
Flutter 3.47 does not yet support Flutter GPU in Windows or Linux release builds.
Import the package and add a MapLibreMap widget.
import 'package:flutter/material.dart';
import 'package:maplibre_flutter_gpu/maplibre_flutter_gpu.dart';
class MapPage extends StatefulWidget {
const MapPage({super.key});
@override
State<MapPage> createState() => _MapPageState();
}
class _MapPageState extends State<MapPage> {
MapLibreMapController? _controller;
@override
Widget build(BuildContext context) => Scaffold(
body: MapLibreMap(
styleString: MapLibreStyles.openfreemapLiberty,
initialCameraPosition: const CameraPosition(
target: LatLng(35.6812, 139.7671),
zoom: 13,
),
onMapCreated: (controller) {
_controller = controller;
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_controller?.animateCamera(
CameraUpdate.newLatLngZoom(
const LatLng(35.6586, 139.7454),
15,
),
);
},
child: const Icon(Icons.location_searching),
),
);
}
MapLibreMap constructor #
Map and lifecycle #
| Parameter | Type | Default | Description |
|---|---|---|---|
initialCameraPosition |
CameraPosition? |
Style camera | Camera used when the map is created. Use cameraPosition or the controller for later changes. |
cameraPosition |
CameraPosition? |
null |
Externally controlled camera. The latest input takes precedence over the initial camera and disables local camera mutations. |
styleString |
String |
MapLibreStyles.demo |
Style URL, raw style JSON, absolute file path, file URI, or Flutter asset path. |
onMapCreated |
MapCreatedCallback? |
null |
Receives the controller after renderer creation. The initial style may still be loading. |
onStyleLoadedCallback |
OnStyleLoadedCallback? |
null |
Runs after the active style loads and after every successful style replacement. |
onCameraMove |
OnCameraMoveCallback? |
null |
Reports camera changes adopted from native frames. |
onFrame |
OnMapFrameCallback? |
null |
Receives immutable camera and viewport metadata when a native frame is adopted for rendering. |
onCameraIdle |
OnCameraIdleCallback? |
null |
Runs after camera movement settles. It does not mean all map work has finished. |
onMapIdle |
OnMapIdleCallback? |
null |
Runs whenever style, camera, and pending map work have fully settled. |
onMapClick |
OnMapClickCallback? |
null |
Reports a tap using logical screen coordinates and geographic coordinates. |
onMapLongClick |
OnMapClickCallback? |
null |
Reports the start of a long press using screen and geographic coordinates. |
Camera and gestures #
| Parameter | Type | Default | Description |
|---|---|---|---|
cameraTargetBounds |
CameraTargetBounds |
No explicit bounds | Limits the geographic camera target. Web Mercator projection limits still apply. |
cameraConstrainMode |
CameraConstrainMode? |
Bounds-dependent | Selects none, heightOnly, or screen viewport correction. Explicit modes require a rebuilt native bridge. |
minMaxZoomPreference |
MinMaxZoomPreference |
0–25.5 | Limits camera zoom. |
minMaxTiltPreference |
MinMaxTiltPreference |
0–60° | Limits camera tilt in degrees. |
rotateGesturesEnabled |
bool |
true |
Enables two-finger rotation. |
scrollGesturesEnabled |
bool |
true |
Enables one-finger panning. |
zoomGesturesEnabled |
bool |
true |
Enables pinch and scroll-wheel zoom. |
tiltGesturesEnabled |
bool |
true |
Enables three-finger vertical tilt. |
doubleClickZoomEnabled |
bool? |
Follows zoom gestures | Enables double-tap zoom independently. |
gestureOptions |
MapGestureOptions |
const MapGestureOptions() |
Configures gesture animation, thresholds, and sensitivity. |
trackCameraPosition |
bool |
false |
Makes the controller notify its listeners when the camera or viewport size changes. |
Map controls #
| Parameter | Type | Default | Description |
|---|---|---|---|
compassEnabled |
bool |
true |
Displays the compass when its builder is available. |
compassViewPosition |
CompassViewPosition? |
Top right | Positions the compass. |
compassViewMargins |
Point<num>? |
(8, 8) |
Sets compass insets in logical pixels. |
compassBuilder |
CompassWidgetBuilder? |
Default compass | Builds the compass from its bearing and reset callback. |
logoEnabled |
bool |
false |
Displays the MapLibre logo when its builder is available. |
logoViewPosition |
LogoViewPosition? |
Bottom left | Positions the logo. |
logoViewMargins |
Point<num>? |
(8, 8) |
Sets logo insets in logical pixels. |
logoBuilder |
WidgetBuilder? |
Default logo | Builds the MapLibre logo. |
attributionButtonEnabled |
bool |
true |
Displays the attribution button. |
attributionButtonPosition |
AttributionButtonPosition? |
Bottom right | Positions the attribution button. |
attributionButtonMargins |
Point<num>? |
(8, 8) |
Sets attribution-button insets in logical pixels. |
onAttributionLinkTap |
AttributionLinkCallback? |
null |
Handles links selected in the default attribution dialog. |
attributionButtonBuilder |
AttributionButtonWidgetBuilder? |
Default button | Builds the attribution button. |
attributionDialogBuilder |
WidgetBuilder? |
Default dialog | Builds the dialog opened by the attribution button. |
scaleControlEnabled |
bool |
false |
Displays a scale bar when its value can be calculated. |
scaleControlPosition |
ScaleControlPosition |
Bottom left | Positions the scale bar. |
scaleControlUnit |
ScaleControlUnit |
Metric | Selects metric or imperial distance. |
scaleControlMargins |
Point<num> |
(8, 8) |
Sets scale-bar insets in logical pixels. |
scaleControlMaxWidth |
double |
80 |
Sets the maximum scale-bar width in logical pixels. |
scaleControlAvoidLogo |
bool |
true |
Moves the scale bar above a logo in the same bottom corner. |
scaleControlLogoOffset |
double |
27 |
Sets the logo avoidance offset in logical pixels. |
scaleControlBuilder |
ScaleControlWidgetBuilder? |
Default scale bar | Builds the scale bar from a ScaleBarValue. |
Flutter widgets and custom GPU content #
| Parameter | Type | Default | Description |
|---|---|---|---|
foregroundLoadColor |
Color? |
Transparent | Value supplied to the loading builder. |
userLocation |
MapUserLocation? |
null |
Displays an application-supplied position with optional heading and horizontal accuracy. |
userLocationBuilder |
MapUserLocationWidgetBuilder? |
Default location marker | Builds a location marker from adopted frame geometry. Return null to hide it. |
overlayBuilder |
MapOverlayWidgetBuilder? |
null |
Builds interactive Flutter content from adopted frame metadata. |
loadingBuilder |
MapLoadingWidgetBuilder? |
Default overlay | Builds the widget displayed while a style loads. |
errorBuilder |
MapErrorWidgetBuilder? |
ErrorWidget |
Builds the replacement widget when map creation fails. |
symbolIconBuilder |
SymbolWidgetBuilder? |
Style sprite | Builds every placed symbol icon as a Flutter widget. Return null to hide one. |
symbolTextBuilder |
SymbolWidgetBuilder? |
Style text | Builds every placed symbol label as a Flutter widget. Return null to hide one. |
symbolFadeDuration |
Duration |
150 ms | Controls symbol fade-in and fade-out. |
symbolCullingPadding |
EdgeInsets |
120 horizontal, 60 vertical | Keeps symbols built slightly outside the viewport. |
symbolCompositingMode |
SymbolCompositingMode |
interleaved |
Preserves style layer order or uses a faster single-surface overlay. |
gpuMapRenderCallback |
MapLibreGpuRenderCallback? |
null |
Records geographic GPU geometry inside the map's 3D sequence with shared depth. |
gpuRenderCallback |
MapLibreGpuRenderCallback? |
null |
Records a final Flutter GPU overlay above the map. |
gpuRepaint |
Listenable? |
null |
Requests new frames for animated custom GPU content. |
gpuOverlayDepthMode |
MapLibreGpuDepthMode |
isolated |
Selects isolated or shared depth for the final GPU overlay. |
fillExtrusionRendererBuilder |
FillExtrusionRendererBuilder? |
null |
Customizes 3D building layers. See Customizing 3D buildings. |
GPU callbacks receive a MapLibreGpuMapTransform.viewProjectionMatrix with
normalized depth from 0 at the near plane to 1 at the far plane, matching the
map's shared depth buffer. Use this matrix directly in Flutter GPU vertex
shaders without another OpenGL depth conversion. Its X/Y inputs are
origin-relative world pixels, and its Z input is altitude in meters.
MapLibreMap creates and owns its MapLibreMapController. Receive the
controller through onMapCreated, then use it to move the camera, change the
style, manage layers, inspect source metadata, and convert between geographic
and screen coordinates. Do not dispose the controller yourself.
Customizing 3D buildings #
fillExtrusionRendererBuilder returns a FillExtrusionRenderer for a
fill-extrusion style layer, or null to keep MapLibre's rendering. The map keeps
tile loading, depth testing, and pass planning. A renderer can:
- hide individual buildings with
isVisible, - shade surfaces with a
FillExtrusionMaterialfragment shader, and - receive each tile's extruded geometry in
didAddTile, for example to build collision shapes or debris effects.
Each FillExtrusionPart is one polygon of a feature. Sources such as
OpenMapTiles merge buildings with equal attributes into one multipolygon, so
parts address individual buildings. Notify repaint after visibility or the
material changes.
class HiddenBuildings extends FillExtrusionRenderer {
HiddenBuildings(this.hidden) : super(repaint: hidden);
/// Hidden buildings by feature identifier and polygon index.
final ValueNotifier<Set<(Object?, int)>> hidden;
@override
bool isVisible(FillExtrusionPart part) =>
!hidden.value.contains((part.featureId, part.polygonIndex));
}
// Create the renderer once, for example in State.initState.
final hidden = ValueNotifier<Set<(Object?, int)>>({});
final buildings = HiddenBuildings(hidden);
MapLibreMap(
styleString: style,
fillExtrusionRendererBuilder: (layer) =>
layer.id == 'building-3d' ? buildings : null,
)
// Later, hide a building and repaint the current frame.
hidden.value = {...hidden.value, (featureId, polygonIndex)};
A material supplies a fragment shader from the application's shader bundle.
The map's vertex shader provides MapLibre's lit color, the tile position, the
outward surface normal with the height fraction, and the ring distance with its
meter scale. Up to eight floats of application data per vertex are available
through customAttributes. See FillExtrusionMaterial for the exact inputs.
User location #
Pass a MapUserLocation to display a location dot, a heading arrow, and an
accuracy area. The application acquires the location and owns permissions and
subscriptions. This package does not start location services or follow the
location automatically.
MapLibreMap(
userLocation: MapUserLocation(
position: const LatLng(35.6812, 139.7671),
headingDegrees: 45,
accuracyMeters: 30,
),
userLocationBuilder: (context, state) => MapUserLocationMarker(
state: state,
color: Colors.deepPurple,
accuracyColor: Colors.deepPurple.withValues(alpha: 0.15),
),
)
The style controls example uses geolocator for
foreground location updates, permission handling, and an explicit follow
control. Panning stops follow while location display continues. Applications
can use any location provider, including recorded or simulated data.
Interactive Flutter markers #
Use overlayBuilder or an external Stack with the controller's coordinate
conversion methods to place Flutter widgets at geographic coordinates.
overlayBuilder runs when map frame metadata changes.
MapLibreMap(
onMapCreated: (controller) => _controller = controller,
overlayBuilder: (context, frame) {
final position = _controller?.toScreenOffset(markerCoordinate);
if (position == null) return null;
return Stack(
children: [
Positioned(
left: position.dx,
top: position.dy,
child: const Icon(Icons.location_pin),
),
],
);
},
)
With an external Stack, listen to the controller and rebuild to update
position when the camera or viewport size changes.
final position = _controller?.toScreenOffset(markerCoordinate);
Stack(
fit: StackFit.expand,
children: [
MapLibreMap(
trackCameraPosition: true,
onMapCreated: (controller) => _controller = controller,
),
if (position != null)
Positioned(
left: position.dx,
top: position.dy,
child: const Icon(Icons.location_pin),
),
],
)
See the example app for controller listener setup and cleanup.
Externally controlled cameras #
Pass cameraPosition when another widget or controller owns the camera.
Changing the value updates the map without animation. Inputs received while
initialization or a frame is pending are coalesced to the latest position.
The current value is reapplied after style changes, viewport changes, and app
resume.
MapLibreMap(
cameraPosition: camera,
cameraConstrainMode: CameraConstrainMode.none,
styleString: MapLibreStyles.openfreemapLiberty,
onFrame: (frame) {
debugPrint('Frame ${frame.sequence}: ${frame.camera}');
},
)
While cameraPosition is non-null, camera gestures and compass reset are
disabled. Controller camera mutations throw StateError. Camera queries,
style operations, and map tap callbacks remain available. Setting the property
to null restores local camera control at the current position.
CameraConstrainMode.none disables viewport correction. Geographic target
bounds, zoom limits, and tilt limits still apply. The native minimum zoom is 0.
Leaving the mode null retains the default correction for the configured bounds.
An explicit mode throws UnsupportedError when the loaded bridge lacks support.
Style-dependent operations #
For operations that depend on the loaded style, wait for
onStyleLoadedCallback.
MapLibreMap(
onMapCreated: (controller) {
_controller = controller;
},
onStyleLoadedCallback: () async {
await _controller?.setLayerVisibility('building-3d', true);
},
)
MapLibreMapController #
The controller is used to change a map after it has been created. Save the
instance received by onMapCreated for later use.
Style and layers #
| Method | Description |
|---|---|
setStyle(styleString) |
Starts loading a style URL, JSON document, file, or Flutter asset. |
getStyle() |
Returns the active style JSON when available. |
getLayerIds() |
Returns the IDs of the loaded style layers. |
getSourceIds() |
Returns the IDs of the loaded style sources. |
getSourceAttributions() |
Returns unique source attribution strings. |
setLayerVisibility(layerId, visible) |
Shows or hides a loaded layer. |
getLayerVisibility(layerId) |
Returns layer visibility, or null when the layer does not exist. |
addFillExtrusionLayer(...) |
Adds a maplibre_gl-compatible fill-extrusion layer. |
addHeatmapLayer(...) |
Adds a heatmap layer from point features in an existing source. |
addHillshadeLayer(...) |
Adds shaded relief from an existing raster-dem source. |
addLayer(...) |
Adds a fill-extrusion, heatmap, or hillshade layer from a LayerProperties object. |
setLayerProperties(layerId, properties) |
Updates the properties of a loaded layer. |
removeLayer(layerId) |
Removes a loaded layer. |
setFilter(layerId, filter) |
Applies a JSON-compatible filter and throws when the layer is missing. |
setLayerFilter(layerId, filter) |
Applies a JSON filter string and reports whether the layer was found. |
getFilter(layerId) |
Returns the parsed filter for a layer. |
Camera and coordinates #
| Method or property | Description |
|---|---|
cameraPosition |
Latest camera position cached by the controller. |
isCameraMoving |
Whether a programmatic camera transition is active. |
moveCamera(update) |
Applies a camera update immediately. |
animateCamera(update, duration: ...) |
Animates to a camera update. |
easeCamera(update, duration: ..., interpolation: ...) |
Applies a camera update with configurable easing. |
queryCameraPosition() |
Refreshes and returns the current camera position. |
toScreenLocation(latLng) |
Converts a geographic coordinate to logical screen pixels. |
toScreenLocationBatch(latLngs) |
Converts multiple geographic coordinates in one call. |
toScreenOffset(latLng) |
Synchronous Offset version of toScreenLocation. |
toLatLng(screenLocation) |
Converts logical screen pixels to a geographic coordinate. |
toLatLngOffset(screenLocation) |
Synchronous Offset version of toLatLng. |
getVisibleRegion() |
Returns the current visible geographic bounds. |
getMetersPerPixelAtLatitude(latitude) |
Returns map resolution at a latitude. |
setCameraBounds(...) |
Animates the camera to fit geographic bounds. |
updateContentInsets(...) |
Updates camera padding in logical pixels. |
resetNorth() |
Resets the camera bearing to north. |
getPlacedLabels() |
Returns the latest native label-placement snapshot. |
isMapIdle |
Whether the native map had no pending work after its last frame. This does not include Flutter fling animation. |
The controller belongs to its MapLibreMap. Do not call dispose() yourself
and do not use it after the map widget has been removed.
See the example app and the standalone examples
for Flutter markers, runtime style controls, heatmap and hillshade layers, and
custom Flutter GPU rendering.
For implementation boundaries and validation commands, see the source layout guide.
This package is currently in beta. API and runtime stability are not guaranteed. Android in particular still needs testing across a wider range of devices and environments.
Bug reports, device compatibility results, and other feedback are welcome in GitHub Issues.
This is an independent community project. It is not affiliated with or endorsed by the MapLibre organization.