diff --git a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md
index 91d16d00e1a4..1fc48b49315b 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.5.10
+
+* Adds support for heatmap layers.
+
## 0.5.9+2
* Restores support for Dart `^3.3.0` and Flutter `^3.19.0`.
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/README.md b/packages/google_maps_flutter/google_maps_flutter_web/README.md
index 1873645d7633..e53fe3506b07 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/README.md
+++ b/packages/google_maps_flutter/google_maps_flutter_web/README.md
@@ -63,6 +63,9 @@ If you need marker clustering support, modify the
tag to load the [js-mar
```
+## Heatmaps
+To use heatmaps, add `&libraries=visualization` to the end of the URL. See [the documentation](https://developers.google.com/maps/documentation/javascript/libraries) for more information.
+
## Limitations of the web version
The following map options are not available in web, because the map doesn't rotate there:
@@ -85,3 +88,13 @@ Indoor and building layers are still not available on the web. Traffic is.
Only Android supports "[Lite Mode](https://developers.google.com/maps/documentation/android-sdk/lite)", so the `liteModeEnabled` constructor argument can't be set to `true` on web apps.
Google Maps for web uses `HtmlElementView` to render maps. When a `GoogleMap` is stacked below other widgets, [`package:pointer_interceptor`](https://www.pub.dev/packages/pointer_interceptor) must be used to capture mouse events on the Flutter overlays. See issue [#73830](https://github.com/flutter/flutter/issues/73830).
+
+## Supported Heatmap Options
+
+| Field | Supported |
+| ---------------------------- | :-------: |
+| Heatmap.dissipating | ✓ |
+| Heatmap.maxIntensity | ✓ |
+| Heatmap.minimumZoomIntensity | x |
+| Heatmap.maximumZoomIntensity | x |
+| HeatmapGradient.colorMapSize | x |
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart
index 7a735062c38d..be68cdf1c714 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart
@@ -26,6 +26,9 @@ gmaps.Map mapShim() => throw UnimplementedError();
MockSpec(
fallbackGenerators: {#googleMap: mapShim},
),
+ MockSpec(
+ fallbackGenerators: {#googleMap: mapShim},
+ ),
MockSpec(
fallbackGenerators: {#googleMap: mapShim},
),
@@ -161,6 +164,20 @@ void main() {
}, throwsAssertionError);
});
+ testWidgets('cannot updateHeatmaps after dispose',
+ (WidgetTester tester) async {
+ controller.dispose();
+
+ expect(() {
+ controller.updateHeatmaps(
+ HeatmapUpdates.from(
+ const {},
+ const {},
+ ),
+ );
+ }, throwsAssertionError);
+ });
+
testWidgets('cannot updatePolygons after dispose',
(WidgetTester tester) async {
controller.dispose();
@@ -229,6 +246,7 @@ void main() {
group('init', () {
late MockCirclesController circles;
+ late MockHeatmapsController heatmaps;
late MockMarkersController markers;
late MockPolygonsController polygons;
late MockPolylinesController polylines;
@@ -237,6 +255,7 @@ void main() {
setUp(() {
circles = MockCirclesController();
+ heatmaps = MockHeatmapsController();
markers = MockMarkersController();
polygons = MockPolygonsController();
polylines = MockPolylinesController();
@@ -249,6 +268,7 @@ void main() {
..debugSetOverrides(
createMap: (_, __) => map,
circles: circles,
+ heatmaps: heatmaps,
markers: markers,
polygons: polygons,
polylines: polylines,
@@ -287,6 +307,7 @@ void main() {
..debugSetOverrides(
createMap: (_, __) => map,
circles: circles,
+ heatmaps: heatmaps,
markers: markers,
polygons: polygons,
polylines: polylines,
@@ -295,6 +316,7 @@ void main() {
..init();
verify(circles.bindToMap(mapId, map));
+ verify(heatmaps.bindToMap(mapId, map));
verify(markers.bindToMap(mapId, map));
verify(polygons.bindToMap(mapId, map));
verify(polylines.bindToMap(mapId, map));
@@ -307,6 +329,17 @@ void main() {
circleId: CircleId('circle-1'),
zIndex: 1234,
),
+ }, heatmaps: {
+ const Heatmap(
+ heatmapId: HeatmapId('heatmap-1'),
+ data: [
+ WeightedLatLng(LatLng(43.355114, -5.851333)),
+ WeightedLatLng(LatLng(43.354797, -5.851860)),
+ WeightedLatLng(LatLng(43.354469, -5.851318)),
+ WeightedLatLng(LatLng(43.354762, -5.850824)),
+ ],
+ radius: HeatmapRadius.fromPixels(20),
+ ),
}, markers: {
const Marker(
markerId: MarkerId('marker-1'),
@@ -352,6 +385,7 @@ void main() {
controller = createController(mapObjects: mapObjects)
..debugSetOverrides(
circles: circles,
+ heatmaps: heatmaps,
markers: markers,
polygons: polygons,
polylines: polylines,
@@ -360,6 +394,7 @@ void main() {
..init();
verify(circles.addCircles(mapObjects.circles));
+ verify(heatmaps.addHeatmaps(mapObjects.heatmaps));
verify(markers.addMarkers(mapObjects.markers));
verify(polygons.addPolygons(mapObjects.polygons));
verify(polylines.addPolylines(mapObjects.polylines));
@@ -670,6 +705,76 @@ void main() {
}));
});
+ testWidgets('updateHeatmaps', (WidgetTester tester) async {
+ final MockHeatmapsController mock = MockHeatmapsController();
+ controller.debugSetOverrides(heatmaps: mock);
+
+ const List heatmapPoints = [
+ WeightedLatLng(LatLng(37.782, -122.447)),
+ WeightedLatLng(LatLng(37.782, -122.445)),
+ WeightedLatLng(LatLng(37.782, -122.443)),
+ WeightedLatLng(LatLng(37.782, -122.441)),
+ WeightedLatLng(LatLng(37.782, -122.439)),
+ WeightedLatLng(LatLng(37.782, -122.437)),
+ WeightedLatLng(LatLng(37.782, -122.435)),
+ WeightedLatLng(LatLng(37.785, -122.447)),
+ WeightedLatLng(LatLng(37.785, -122.445)),
+ WeightedLatLng(LatLng(37.785, -122.443)),
+ WeightedLatLng(LatLng(37.785, -122.441)),
+ WeightedLatLng(LatLng(37.785, -122.439)),
+ WeightedLatLng(LatLng(37.785, -122.437)),
+ WeightedLatLng(LatLng(37.785, -122.435))
+ ];
+
+ final Set previous = {
+ const Heatmap(
+ heatmapId: HeatmapId('to-be-updated'),
+ data: heatmapPoints,
+ radius: HeatmapRadius.fromPixels(20),
+ ),
+ const Heatmap(
+ heatmapId: HeatmapId('to-be-removed'),
+ data: heatmapPoints,
+ radius: HeatmapRadius.fromPixels(20),
+ ),
+ };
+
+ final Set current = {
+ const Heatmap(
+ heatmapId: HeatmapId('to-be-updated'),
+ data: heatmapPoints,
+ dissipating: false,
+ radius: HeatmapRadius.fromPixels(20),
+ ),
+ const Heatmap(
+ heatmapId: HeatmapId('to-be-added'),
+ data: heatmapPoints,
+ radius: HeatmapRadius.fromPixels(20),
+ ),
+ };
+
+ controller.updateHeatmaps(HeatmapUpdates.from(previous, current));
+
+ verify(mock.removeHeatmaps({
+ const HeatmapId('to-be-removed'),
+ }));
+ verify(mock.addHeatmaps({
+ const Heatmap(
+ heatmapId: HeatmapId('to-be-added'),
+ data: heatmapPoints,
+ radius: HeatmapRadius.fromPixels(20),
+ ),
+ }));
+ verify(mock.changeHeatmaps({
+ const Heatmap(
+ heatmapId: HeatmapId('to-be-updated'),
+ data: heatmapPoints,
+ dissipating: false,
+ radius: HeatmapRadius.fromPixels(20),
+ ),
+ }));
+ });
+
testWidgets('updateMarkers', (WidgetTester tester) async {
final MockMarkersController mock = MockMarkersController();
controller = createController()..debugSetOverrides(markers: mock);
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart
index 832b799724e8..ca64a2a9d7f3 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart
@@ -114,6 +114,95 @@ class MockCirclesController extends _i1.Mock implements _i2.CirclesController {
);
}
+/// A class which mocks [HeatmapsController].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockHeatmapsController extends _i1.Mock
+ implements _i2.HeatmapsController {
+ @override
+ Map<_i3.HeatmapId, _i2.HeatmapController> get heatmaps => (super.noSuchMethod(
+ Invocation.getter(#heatmaps),
+ returnValue: <_i3.HeatmapId, _i2.HeatmapController>{},
+ returnValueForMissingStub: <_i3.HeatmapId, _i2.HeatmapController>{},
+ ) as Map<_i3.HeatmapId, _i2.HeatmapController>);
+
+ @override
+ _i4.Map get googleMap => (super.noSuchMethod(
+ Invocation.getter(#googleMap),
+ returnValue: _i5.mapShim(),
+ returnValueForMissingStub: _i5.mapShim(),
+ ) as _i4.Map);
+
+ @override
+ set googleMap(_i4.Map? _googleMap) => super.noSuchMethod(
+ Invocation.setter(
+ #googleMap,
+ _googleMap,
+ ),
+ returnValueForMissingStub: null,
+ );
+
+ @override
+ int get mapId => (super.noSuchMethod(
+ Invocation.getter(#mapId),
+ returnValue: 0,
+ returnValueForMissingStub: 0,
+ ) as int);
+
+ @override
+ set mapId(int? _mapId) => super.noSuchMethod(
+ Invocation.setter(
+ #mapId,
+ _mapId,
+ ),
+ returnValueForMissingStub: null,
+ );
+
+ @override
+ void addHeatmaps(Set<_i3.Heatmap>? heatmapsToAdd) => super.noSuchMethod(
+ Invocation.method(
+ #addHeatmaps,
+ [heatmapsToAdd],
+ ),
+ returnValueForMissingStub: null,
+ );
+
+ @override
+ void changeHeatmaps(Set<_i3.Heatmap>? heatmapsToChange) => super.noSuchMethod(
+ Invocation.method(
+ #changeHeatmaps,
+ [heatmapsToChange],
+ ),
+ returnValueForMissingStub: null,
+ );
+
+ @override
+ void removeHeatmaps(Set<_i3.HeatmapId>? heatmapIdsToRemove) =>
+ super.noSuchMethod(
+ Invocation.method(
+ #removeHeatmaps,
+ [heatmapIdsToRemove],
+ ),
+ returnValueForMissingStub: null,
+ );
+
+ @override
+ void bindToMap(
+ int? mapId,
+ _i4.Map? googleMap,
+ ) =>
+ super.noSuchMethod(
+ Invocation.method(
+ #bindToMap,
+ [
+ mapId,
+ googleMap,
+ ],
+ ),
+ returnValueForMissingStub: null,
+ );
+}
+
/// A class which mocks [PolygonsController].
///
/// See the documentation for Mockito's code generation for more information.
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart
index de813b2efae6..1e02f576a089 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart
@@ -263,6 +263,16 @@ void main() {
verify(controller.updateCircles(expectedUpdates));
});
+ testWidgets('updateHeatmaps', (WidgetTester tester) async {
+ final HeatmapUpdates expectedUpdates = HeatmapUpdates.from(
+ const {},
+ const {},
+ );
+
+ await plugin.updateHeatmaps(expectedUpdates, mapId: mapId);
+
+ verify(controller.updateHeatmaps(expectedUpdates));
+ });
// Tile Overlays
testWidgets('updateTileOverlays', (WidgetTester tester) async {
final Set expectedOverlays = {
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart
index 2da0a46f13f2..cf5acfcb813c 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart
@@ -137,6 +137,7 @@ class MockGoogleMapController extends _i1.Mock
_i4.DebugSetOptionsFunction? setOptions,
_i4.MarkersController? markers,
_i4.CirclesController? circles,
+ _i4.HeatmapsController? heatmaps,
_i4.PolygonsController? polygons,
_i4.PolylinesController? polylines,
_i6.ClusterManagersController? clusterManagers,
@@ -151,6 +152,7 @@ class MockGoogleMapController extends _i1.Mock
#setOptions: setOptions,
#markers: markers,
#circles: circles,
+ #heatmaps: heatmaps,
#polygons: polygons,
#polylines: polylines,
#clusterManagers: clusterManagers,
@@ -289,6 +291,15 @@ class MockGoogleMapController extends _i1.Mock
returnValueForMissingStub: null,
);
+ @override
+ void updateHeatmaps(_i2.HeatmapUpdates? updates) => super.noSuchMethod(
+ Invocation.method(
+ #updateHeatmaps,
+ [updates],
+ ),
+ returnValueForMissingStub: null,
+ );
+
@override
void updatePolygons(_i2.PolygonUpdates? updates) => super.noSuchMethod(
Invocation.method(
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shape_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shape_test.dart
index b8da855de840..0912fcbaec4d 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shape_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shape_test.dart
@@ -3,9 +3,11 @@
// found in the LICENSE file.
import 'dart:async';
+import 'dart:js_interop';
import 'package:flutter_test/flutter_test.dart';
import 'package:google_maps/google_maps.dart' as gmaps;
+import 'package:google_maps/google_maps_visualization.dart' as visualization;
import 'package:google_maps_flutter_web/google_maps_flutter_web.dart';
import 'package:integration_test/integration_test.dart';
@@ -216,4 +218,51 @@ void main() {
});
});
});
+
+ group('HeatmapController', () {
+ late visualization.HeatmapLayer heatmap;
+
+ setUp(() {
+ heatmap = visualization.HeatmapLayer();
+ });
+
+ testWidgets('update', (WidgetTester tester) async {
+ final HeatmapController controller = HeatmapController(heatmap: heatmap);
+ final visualization.HeatmapLayerOptions options =
+ visualization.HeatmapLayerOptions()
+ ..data = [gmaps.LatLng(0, 0)].toJS;
+
+ expect(heatmap.data, hasLength(0));
+
+ controller.update(options);
+
+ expect(heatmap.data, hasLength(1));
+ });
+
+ group('remove', () {
+ late HeatmapController controller;
+
+ setUp(() {
+ controller = HeatmapController(heatmap: heatmap);
+ });
+
+ testWidgets('drops gmaps instance', (WidgetTester tester) async {
+ controller.remove();
+
+ expect(controller.heatmap, isNull);
+ });
+
+ testWidgets('cannot call update after remove',
+ (WidgetTester tester) async {
+ final visualization.HeatmapLayerOptions options =
+ visualization.HeatmapLayerOptions()..dissipating = true;
+
+ controller.remove();
+
+ expect(() {
+ controller.update(options);
+ }, throwsAssertionError);
+ });
+ });
+ });
}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shapes_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shapes_test.dart
index a6492a5b7bd6..ec8198679d55 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shapes_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shapes_test.dart
@@ -9,6 +9,7 @@ import 'dart:ui';
import 'package:flutter_test/flutter_test.dart';
import 'package:google_maps/google_maps.dart' as gmaps;
import 'package:google_maps/google_maps_geometry.dart' as geometry;
+import 'package:google_maps/google_maps_visualization.dart' as visualization;
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
import 'package:google_maps_flutter_web/google_maps_flutter_web.dart';
// ignore: implementation_imports
@@ -375,4 +376,143 @@ void main() {
closeTo(0.5, _acceptableDelta));
});
});
+
+ group('HeatmapsController', () {
+ late HeatmapsController controller;
+
+ const List heatmapPoints = [
+ WeightedLatLng(LatLng(37.782, -122.447)),
+ WeightedLatLng(LatLng(37.782, -122.445)),
+ WeightedLatLng(LatLng(37.782, -122.443)),
+ WeightedLatLng(LatLng(37.782, -122.441)),
+ WeightedLatLng(LatLng(37.782, -122.439)),
+ WeightedLatLng(LatLng(37.782, -122.437)),
+ WeightedLatLng(LatLng(37.782, -122.435)),
+ WeightedLatLng(LatLng(37.785, -122.447)),
+ WeightedLatLng(LatLng(37.785, -122.445)),
+ WeightedLatLng(LatLng(37.785, -122.443)),
+ WeightedLatLng(LatLng(37.785, -122.441)),
+ WeightedLatLng(LatLng(37.785, -122.439)),
+ WeightedLatLng(LatLng(37.785, -122.437)),
+ WeightedLatLng(LatLng(37.785, -122.435))
+ ];
+
+ setUp(() {
+ controller = HeatmapsController();
+ controller.bindToMap(123, map);
+ });
+
+ testWidgets('addHeatmaps', (WidgetTester tester) async {
+ final Set heatmaps = {
+ const Heatmap(
+ heatmapId: HeatmapId('1'),
+ data: heatmapPoints,
+ radius: HeatmapRadius.fromPixels(20),
+ ),
+ const Heatmap(
+ heatmapId: HeatmapId('2'),
+ data: heatmapPoints,
+ radius: HeatmapRadius.fromPixels(20),
+ ),
+ };
+
+ controller.addHeatmaps(heatmaps);
+
+ expect(controller.heatmaps.length, 2);
+ expect(controller.heatmaps, contains(const HeatmapId('1')));
+ expect(controller.heatmaps, contains(const HeatmapId('2')));
+ expect(controller.heatmaps, isNot(contains(const HeatmapId('66'))));
+ });
+
+ testWidgets('changeHeatmaps', (WidgetTester tester) async {
+ final Set heatmaps = {
+ const Heatmap(
+ heatmapId: HeatmapId('1'),
+ data: [],
+ radius: HeatmapRadius.fromPixels(20),
+ ),
+ };
+ controller.addHeatmaps(heatmaps);
+
+ expect(
+ controller.heatmaps[const HeatmapId('1')]?.heatmap?.data,
+ hasLength(0),
+ );
+
+ final Set updatedHeatmaps = {
+ const Heatmap(
+ heatmapId: HeatmapId('1'),
+ data: [WeightedLatLng(LatLng(0, 0))],
+ radius: HeatmapRadius.fromPixels(20),
+ ),
+ };
+ controller.changeHeatmaps(updatedHeatmaps);
+
+ expect(controller.heatmaps.length, 1);
+ expect(
+ controller.heatmaps[const HeatmapId('1')]?.heatmap?.data,
+ hasLength(1),
+ );
+ });
+
+ testWidgets('removeHeatmaps', (WidgetTester tester) async {
+ final Set heatmaps = {
+ const Heatmap(
+ heatmapId: HeatmapId('1'),
+ data: heatmapPoints,
+ radius: HeatmapRadius.fromPixels(20),
+ ),
+ const Heatmap(
+ heatmapId: HeatmapId('2'),
+ data: heatmapPoints,
+ radius: HeatmapRadius.fromPixels(20),
+ ),
+ const Heatmap(
+ heatmapId: HeatmapId('3'),
+ data: heatmapPoints,
+ radius: HeatmapRadius.fromPixels(20),
+ ),
+ };
+
+ controller.addHeatmaps(heatmaps);
+
+ expect(controller.heatmaps.length, 3);
+
+ // Remove some polylines...
+ final Set heatmapIdsToRemove = {
+ const HeatmapId('1'),
+ const HeatmapId('3'),
+ };
+
+ controller.removeHeatmaps(heatmapIdsToRemove);
+
+ expect(controller.heatmaps.length, 1);
+ expect(controller.heatmaps, isNot(contains(const HeatmapId('1'))));
+ expect(controller.heatmaps, contains(const HeatmapId('2')));
+ expect(controller.heatmaps, isNot(contains(const HeatmapId('3'))));
+ });
+
+ testWidgets('Converts colors to CSS', (WidgetTester tester) async {
+ final Set heatmaps = {
+ const Heatmap(
+ heatmapId: HeatmapId('1'),
+ data: heatmapPoints,
+ gradient: HeatmapGradient(
+ [HeatmapGradientColor(Color(0xFFFABADA), 0)],
+ ),
+ radius: HeatmapRadius.fromPixels(20),
+ ),
+ };
+
+ controller.addHeatmaps(heatmaps);
+
+ final visualization.HeatmapLayer heatmap =
+ controller.heatmaps.values.first.heatmap!;
+
+ expect(
+ heatmap.get('gradient'),
+ ['rgba(250, 186, 218, 0.00)', 'rgba(250, 186, 218, 1.00)'],
+ );
+ });
+ });
}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml
index 2b96e1d5cab1..a883f5f75218 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml
@@ -8,7 +8,7 @@ environment:
dependencies:
flutter:
sdk: flutter
- google_maps_flutter_platform_interface: ^2.7.0
+ google_maps_flutter_platform_interface: ^2.9.0
google_maps_flutter_web:
path: ../
web: ^1.0.0
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/web/index.html b/packages/google_maps_flutter/google_maps_flutter_web/example/web/index.html
index fc6ddfcfa7b4..51b7253b8e1f 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/web/index.html
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/web/index.html
@@ -6,7 +6,7 @@
Browser Tests
-
+
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart
index cda20cf9f526..f56e0b02bd1b 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart
@@ -16,6 +16,7 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'package:google_maps/google_maps.dart' as gmaps;
+import 'package:google_maps/google_maps_visualization.dart' as visualization;
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
import 'package:sanitize_html/sanitize_html.dart';
import 'package:stream_transform/stream_transform.dart';
@@ -34,6 +35,8 @@ part 'src/circles.dart';
part 'src/convert.dart';
part 'src/google_maps_controller.dart';
part 'src/google_maps_flutter_web.dart';
+part 'src/heatmap.dart';
+part 'src/heatmaps.dart';
part 'src/marker.dart';
part 'src/markers.dart';
part 'src/overlay.dart';
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart
index dda30f23fa62..b8ec01d3c111 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart
@@ -32,6 +32,11 @@ double _getCssOpacity(Color color) {
return color.opacity;
}
+// Converts a [Color] into a valid CSS value rgba(R, G, B, A).
+String _getCssColorWithAlpha(Color color) {
+ return 'rgba(${color.red}, ${color.green}, ${color.blue}, ${(color.alpha / 255).toStringAsFixed(2)})';
+}
+
// Converts options from the plugin into gmaps.MapOptions that can be used by the JS SDK.
// The following options are not handled here, for various reasons:
// The following are not available in web, because the map doesn't rotate there:
@@ -475,6 +480,33 @@ gmaps.CircleOptions _circleOptionsFromCircle(Circle circle) {
return circleOptions;
}
+visualization.HeatmapLayerOptions _heatmapOptionsFromHeatmap(Heatmap heatmap) {
+ final Iterable? gradientColors =
+ heatmap.gradient?.colors.map((HeatmapGradientColor e) => e.color);
+ final visualization.HeatmapLayerOptions heatmapOptions =
+ visualization.HeatmapLayerOptions()
+ ..data = heatmap.data
+ .map(
+ (WeightedLatLng e) => visualization.WeightedLocation()
+ ..location = gmaps.LatLng(e.point.latitude, e.point.longitude)
+ ..weight = e.weight,
+ )
+ .toList()
+ .toJS
+ ..dissipating = heatmap.dissipating
+ ..gradient = gradientColors == null
+ ? null
+ : [
+ // Web needs a first color with 0 alpha
+ gradientColors.first.withAlpha(0),
+ ...gradientColors,
+ ].map(_getCssColorWithAlpha).toList()
+ ..maxIntensity = heatmap.maxIntensity
+ ..opacity = heatmap.opacity
+ ..radius = heatmap.radius.radius;
+ return heatmapOptions;
+}
+
gmaps.PolygonOptions _polygonOptionsFromPolygon(
gmaps.Map googleMap, Polygon polygon) {
// Convert all points to GmLatLng
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart
index 3baeef8851ad..89f5e33e2387 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart
@@ -30,9 +30,11 @@ class GoogleMapController {
_polylines = mapObjects.polylines,
_circles = mapObjects.circles,
_clusterManagers = mapObjects.clusterManagers,
+ _heatmaps = mapObjects.heatmaps,
_tileOverlays = mapObjects.tileOverlays,
_lastMapConfiguration = mapConfiguration {
_circlesController = CirclesController(stream: _streamController);
+ _heatmapsController = HeatmapsController();
_polygonsController = PolygonsController(stream: _streamController);
_polylinesController = PolylinesController(stream: _streamController);
_clusterManagersController =
@@ -66,6 +68,7 @@ class GoogleMapController {
final Set _polylines;
final Set _circles;
final Set _clusterManagers;
+ final Set _heatmaps;
Set _tileOverlays;
// The configuration passed by the user, before converting to gmaps.
@@ -122,6 +125,7 @@ class GoogleMapController {
// Geometry controllers, for different features of the map.
CirclesController? _circlesController;
+ HeatmapsController? _heatmapsController;
PolygonsController? _polygonsController;
PolylinesController? _polylinesController;
MarkersController? _markersController;
@@ -146,6 +150,7 @@ class GoogleMapController {
DebugSetOptionsFunction? setOptions,
MarkersController? markers,
CirclesController? circles,
+ HeatmapsController? heatmaps,
PolygonsController? polygons,
PolylinesController? polylines,
ClusterManagersController? clusterManagers,
@@ -155,6 +160,7 @@ class GoogleMapController {
_overrideSetOptions = setOptions;
_markersController = markers ?? _markersController;
_circlesController = circles ?? _circlesController;
+ _heatmapsController = heatmaps ?? _heatmapsController;
_polygonsController = polygons ?? _polygonsController;
_polylinesController = polylines ?? _polylinesController;
_clusterManagersController = clusterManagers ?? _clusterManagersController;
@@ -263,6 +269,8 @@ class GoogleMapController {
// null.
assert(_circlesController != null,
'Cannot attach a map to a null CirclesController instance.');
+ assert(_heatmapsController != null,
+ 'Cannot attach a map to a null HeatmapsController instance.');
assert(_polygonsController != null,
'Cannot attach a map to a null PolygonsController instance.');
assert(_polylinesController != null,
@@ -275,6 +283,7 @@ class GoogleMapController {
'Cannot attach a map to a null TileOverlaysController instance.');
_circlesController!.bindToMap(_mapId, map);
+ _heatmapsController!.bindToMap(_mapId, map);
_polygonsController!.bindToMap(_mapId, map);
_polylinesController!.bindToMap(_mapId, map);
_markersController!.bindToMap(_mapId, map);
@@ -301,6 +310,7 @@ class GoogleMapController {
_markersController!.addMarkers(_markers);
_circlesController!.addCircles(_circles);
+ _heatmapsController!.addHeatmaps(_heatmaps);
_polygonsController!.addPolygons(_polygons);
_polylinesController!.addPolylines(_polylines);
_tileOverlaysController!.addTileOverlays(_tileOverlays);
@@ -439,12 +449,25 @@ class GoogleMapController {
/// Applies [CircleUpdates] to the currently managed circles.
void updateCircles(CircleUpdates updates) {
assert(
- _circlesController != null, 'Cannot update circles after dispose().');
+ _circlesController != null,
+ 'Cannot update circles after dispose().',
+ );
_circlesController?.addCircles(updates.circlesToAdd);
_circlesController?.changeCircles(updates.circlesToChange);
_circlesController?.removeCircles(updates.circleIdsToRemove);
}
+ /// Applies [HeatmapUpdates] to the currently managed heatmaps.
+ void updateHeatmaps(HeatmapUpdates updates) {
+ assert(
+ _heatmapsController != null,
+ 'Cannot update heatmaps after dispose().',
+ );
+ _heatmapsController?.addHeatmaps(updates.heatmapsToAdd);
+ _heatmapsController?.changeHeatmaps(updates.heatmapsToChange);
+ _heatmapsController?.removeHeatmaps(updates.heatmapIdsToRemove);
+ }
+
/// Applies [PolygonUpdates] to the currently managed polygons.
void updatePolygons(PolygonUpdates updates) {
assert(
@@ -531,6 +554,7 @@ class GoogleMapController {
_widget = null;
_googleMap = null;
_circlesController = null;
+ _heatmapsController = null;
_polygonsController = null;
_polylinesController = null;
_markersController = null;
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart
index 16618b59a6e8..c49b5ed67392 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart
@@ -90,6 +90,15 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform {
_map(mapId).updateCircles(circleUpdates);
}
+ /// Applies the passed in `heatmapUpdates` to the `mapId`.
+ @override
+ Future updateHeatmaps(
+ HeatmapUpdates heatmapUpdates, {
+ required int mapId,
+ }) async {
+ _map(mapId).updateHeatmaps(heatmapUpdates);
+ }
+
@override
Future updateTileOverlays({
required Set newTileOverlays,
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/heatmap.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/heatmap.dart
new file mode 100644
index 000000000000..1e792f44ca17
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/heatmap.dart
@@ -0,0 +1,34 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of '../google_maps_flutter_web.dart';
+
+/// The `HeatmapController` class wraps a [visualization.HeatmapLayer] and its `onTap` behavior.
+class HeatmapController {
+ /// Creates a `HeatmapController`, which wraps a [visualization.HeatmapLayer] object and its `onTap` behavior.
+ HeatmapController({required visualization.HeatmapLayer heatmap})
+ : _heatmap = heatmap;
+
+ visualization.HeatmapLayer? _heatmap;
+
+ /// Returns the wrapped [visualization.HeatmapLayer]. Only used for testing.
+ @visibleForTesting
+ visualization.HeatmapLayer? get heatmap => _heatmap;
+
+ /// Updates the options of the wrapped [visualization.HeatmapLayer] object.
+ ///
+ /// This cannot be called after [remove].
+ void update(visualization.HeatmapLayerOptions options) {
+ assert(_heatmap != null, 'Cannot `update` Heatmap after calling `remove`.');
+ _heatmap!.options = options;
+ }
+
+ /// Disposes of the currently wrapped [visualization.HeatmapLayer].
+ void remove() {
+ if (_heatmap != null) {
+ _heatmap!.map = null;
+ _heatmap = null;
+ }
+ }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/heatmaps.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/heatmaps.dart
new file mode 100644
index 000000000000..2d235515a297
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/heatmaps.dart
@@ -0,0 +1,57 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of '../google_maps_flutter_web.dart';
+
+/// This class manages all the [HeatmapController]s associated to a [GoogleMapController].
+class HeatmapsController extends GeometryController {
+ /// Initialize the cache
+ HeatmapsController()
+ : _heatmapIdToController = {};
+
+ // A cache of [HeatmapController]s indexed by their [HeatmapId].
+ final Map _heatmapIdToController;
+
+ /// Returns the cache of [HeatmapController]s. Test only.
+ @visibleForTesting
+ Map get heatmaps => _heatmapIdToController;
+
+ /// Adds a set of [Heatmap] objects to the cache.
+ ///
+ /// Wraps each [Heatmap] into its corresponding [HeatmapController].
+ void addHeatmaps(Set heatmapsToAdd) {
+ heatmapsToAdd.forEach(_addHeatmap);
+ }
+
+ void _addHeatmap(Heatmap heatmap) {
+ final visualization.HeatmapLayerOptions heatmapOptions =
+ _heatmapOptionsFromHeatmap(heatmap);
+ final visualization.HeatmapLayer gmHeatmap =
+ visualization.HeatmapLayer(heatmapOptions);
+ gmHeatmap.map = googleMap;
+ final HeatmapController controller = HeatmapController(heatmap: gmHeatmap);
+ _heatmapIdToController[heatmap.heatmapId] = controller;
+ }
+
+ /// Updates a set of [Heatmap] objects with new options.
+ void changeHeatmaps(Set heatmapsToChange) {
+ heatmapsToChange.forEach(_changeHeatmap);
+ }
+
+ void _changeHeatmap(Heatmap heatmap) {
+ final HeatmapController? heatmapController =
+ _heatmapIdToController[heatmap.heatmapId];
+ heatmapController?.update(_heatmapOptionsFromHeatmap(heatmap));
+ }
+
+ /// Removes a set of [HeatmapId]s from the cache.
+ void removeHeatmaps(Set heatmapIdsToRemove) {
+ for (final HeatmapId heatmapId in heatmapIdsToRemove) {
+ final HeatmapController? heatmapController =
+ _heatmapIdToController[heatmapId];
+ heatmapController?.remove();
+ _heatmapIdToController.remove(heatmapId);
+ }
+ }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml
index a07eab5ed6bd..8240c1b632f3 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml
@@ -2,7 +2,7 @@ name: google_maps_flutter_web
description: Web platform implementation of google_maps_flutter
repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_web
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22
-version: 0.5.9+2
+version: 0.5.10
environment:
sdk: ^3.3.0
@@ -23,7 +23,7 @@ dependencies:
flutter_web_plugins:
sdk: flutter
google_maps: ^8.0.0
- google_maps_flutter_platform_interface: ^2.7.0
+ google_maps_flutter_platform_interface: ^2.9.0
sanitize_html: ^2.0.0
stream_transform: ^2.0.0
web: ">=0.5.1 <2.0.0"