Skip to content

Commit 57fcda0

Browse files
authored
[google_maps_flutter_web] Web changes to support heatmaps (#7315)
Sequel to: - #7312 Prequel to: - #3257
1 parent c6a30bc commit 57fcda0

17 files changed

+585
-5
lines changed

packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.5.10
2+
3+
* Adds support for heatmap layers.
4+
15
## 0.5.9+2
26

37
* Restores support for Dart `^3.3.0` and Flutter `^3.19.0`.

packages/google_maps_flutter/google_maps_flutter_web/README.md

+13
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ If you need marker clustering support, modify the <head> tag to load the [js-mar
6363
</head>
6464
```
6565

66+
## Heatmaps
67+
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.
68+
6669
## Limitations of the web version
6770

6871
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.
8588
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.
8689

8790
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).
91+
92+
## Supported Heatmap Options
93+
94+
| Field | Supported |
95+
| ---------------------------- | :-------: |
96+
| Heatmap.dissipating ||
97+
| Heatmap.maxIntensity ||
98+
| Heatmap.minimumZoomIntensity | x |
99+
| Heatmap.maximumZoomIntensity | x |
100+
| HeatmapGradient.colorMapSize | x |

packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart

+105
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ gmaps.Map mapShim() => throw UnimplementedError();
2626
MockSpec<CirclesController>(
2727
fallbackGenerators: <Symbol, Function>{#googleMap: mapShim},
2828
),
29+
MockSpec<HeatmapsController>(
30+
fallbackGenerators: <Symbol, Function>{#googleMap: mapShim},
31+
),
2932
MockSpec<PolygonsController>(
3033
fallbackGenerators: <Symbol, Function>{#googleMap: mapShim},
3134
),
@@ -161,6 +164,20 @@ void main() {
161164
}, throwsAssertionError);
162165
});
163166

167+
testWidgets('cannot updateHeatmaps after dispose',
168+
(WidgetTester tester) async {
169+
controller.dispose();
170+
171+
expect(() {
172+
controller.updateHeatmaps(
173+
HeatmapUpdates.from(
174+
const <Heatmap>{},
175+
const <Heatmap>{},
176+
),
177+
);
178+
}, throwsAssertionError);
179+
});
180+
164181
testWidgets('cannot updatePolygons after dispose',
165182
(WidgetTester tester) async {
166183
controller.dispose();
@@ -229,6 +246,7 @@ void main() {
229246

230247
group('init', () {
231248
late MockCirclesController circles;
249+
late MockHeatmapsController heatmaps;
232250
late MockMarkersController markers;
233251
late MockPolygonsController polygons;
234252
late MockPolylinesController polylines;
@@ -237,6 +255,7 @@ void main() {
237255

238256
setUp(() {
239257
circles = MockCirclesController();
258+
heatmaps = MockHeatmapsController();
240259
markers = MockMarkersController();
241260
polygons = MockPolygonsController();
242261
polylines = MockPolylinesController();
@@ -249,6 +268,7 @@ void main() {
249268
..debugSetOverrides(
250269
createMap: (_, __) => map,
251270
circles: circles,
271+
heatmaps: heatmaps,
252272
markers: markers,
253273
polygons: polygons,
254274
polylines: polylines,
@@ -287,6 +307,7 @@ void main() {
287307
..debugSetOverrides(
288308
createMap: (_, __) => map,
289309
circles: circles,
310+
heatmaps: heatmaps,
290311
markers: markers,
291312
polygons: polygons,
292313
polylines: polylines,
@@ -295,6 +316,7 @@ void main() {
295316
..init();
296317

297318
verify(circles.bindToMap(mapId, map));
319+
verify(heatmaps.bindToMap(mapId, map));
298320
verify(markers.bindToMap(mapId, map));
299321
verify(polygons.bindToMap(mapId, map));
300322
verify(polylines.bindToMap(mapId, map));
@@ -307,6 +329,17 @@ void main() {
307329
circleId: CircleId('circle-1'),
308330
zIndex: 1234,
309331
),
332+
}, heatmaps: <Heatmap>{
333+
const Heatmap(
334+
heatmapId: HeatmapId('heatmap-1'),
335+
data: <WeightedLatLng>[
336+
WeightedLatLng(LatLng(43.355114, -5.851333)),
337+
WeightedLatLng(LatLng(43.354797, -5.851860)),
338+
WeightedLatLng(LatLng(43.354469, -5.851318)),
339+
WeightedLatLng(LatLng(43.354762, -5.850824)),
340+
],
341+
radius: HeatmapRadius.fromPixels(20),
342+
),
310343
}, markers: <Marker>{
311344
const Marker(
312345
markerId: MarkerId('marker-1'),
@@ -352,6 +385,7 @@ void main() {
352385
controller = createController(mapObjects: mapObjects)
353386
..debugSetOverrides(
354387
circles: circles,
388+
heatmaps: heatmaps,
355389
markers: markers,
356390
polygons: polygons,
357391
polylines: polylines,
@@ -360,6 +394,7 @@ void main() {
360394
..init();
361395

362396
verify(circles.addCircles(mapObjects.circles));
397+
verify(heatmaps.addHeatmaps(mapObjects.heatmaps));
363398
verify(markers.addMarkers(mapObjects.markers));
364399
verify(polygons.addPolygons(mapObjects.polygons));
365400
verify(polylines.addPolylines(mapObjects.polylines));
@@ -670,6 +705,76 @@ void main() {
670705
}));
671706
});
672707

708+
testWidgets('updateHeatmaps', (WidgetTester tester) async {
709+
final MockHeatmapsController mock = MockHeatmapsController();
710+
controller.debugSetOverrides(heatmaps: mock);
711+
712+
const List<WeightedLatLng> heatmapPoints = <WeightedLatLng>[
713+
WeightedLatLng(LatLng(37.782, -122.447)),
714+
WeightedLatLng(LatLng(37.782, -122.445)),
715+
WeightedLatLng(LatLng(37.782, -122.443)),
716+
WeightedLatLng(LatLng(37.782, -122.441)),
717+
WeightedLatLng(LatLng(37.782, -122.439)),
718+
WeightedLatLng(LatLng(37.782, -122.437)),
719+
WeightedLatLng(LatLng(37.782, -122.435)),
720+
WeightedLatLng(LatLng(37.785, -122.447)),
721+
WeightedLatLng(LatLng(37.785, -122.445)),
722+
WeightedLatLng(LatLng(37.785, -122.443)),
723+
WeightedLatLng(LatLng(37.785, -122.441)),
724+
WeightedLatLng(LatLng(37.785, -122.439)),
725+
WeightedLatLng(LatLng(37.785, -122.437)),
726+
WeightedLatLng(LatLng(37.785, -122.435))
727+
];
728+
729+
final Set<Heatmap> previous = <Heatmap>{
730+
const Heatmap(
731+
heatmapId: HeatmapId('to-be-updated'),
732+
data: heatmapPoints,
733+
radius: HeatmapRadius.fromPixels(20),
734+
),
735+
const Heatmap(
736+
heatmapId: HeatmapId('to-be-removed'),
737+
data: heatmapPoints,
738+
radius: HeatmapRadius.fromPixels(20),
739+
),
740+
};
741+
742+
final Set<Heatmap> current = <Heatmap>{
743+
const Heatmap(
744+
heatmapId: HeatmapId('to-be-updated'),
745+
data: heatmapPoints,
746+
dissipating: false,
747+
radius: HeatmapRadius.fromPixels(20),
748+
),
749+
const Heatmap(
750+
heatmapId: HeatmapId('to-be-added'),
751+
data: heatmapPoints,
752+
radius: HeatmapRadius.fromPixels(20),
753+
),
754+
};
755+
756+
controller.updateHeatmaps(HeatmapUpdates.from(previous, current));
757+
758+
verify(mock.removeHeatmaps(<HeatmapId>{
759+
const HeatmapId('to-be-removed'),
760+
}));
761+
verify(mock.addHeatmaps(<Heatmap>{
762+
const Heatmap(
763+
heatmapId: HeatmapId('to-be-added'),
764+
data: heatmapPoints,
765+
radius: HeatmapRadius.fromPixels(20),
766+
),
767+
}));
768+
verify(mock.changeHeatmaps(<Heatmap>{
769+
const Heatmap(
770+
heatmapId: HeatmapId('to-be-updated'),
771+
data: heatmapPoints,
772+
dissipating: false,
773+
radius: HeatmapRadius.fromPixels(20),
774+
),
775+
}));
776+
});
777+
673778
testWidgets('updateMarkers', (WidgetTester tester) async {
674779
final MockMarkersController mock = MockMarkersController();
675780
controller = createController()..debugSetOverrides(markers: mock);

packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart

+89
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,95 @@ class MockCirclesController extends _i1.Mock implements _i2.CirclesController {
114114
);
115115
}
116116

117+
/// A class which mocks [HeatmapsController].
118+
///
119+
/// See the documentation for Mockito's code generation for more information.
120+
class MockHeatmapsController extends _i1.Mock
121+
implements _i2.HeatmapsController {
122+
@override
123+
Map<_i3.HeatmapId, _i2.HeatmapController> get heatmaps => (super.noSuchMethod(
124+
Invocation.getter(#heatmaps),
125+
returnValue: <_i3.HeatmapId, _i2.HeatmapController>{},
126+
returnValueForMissingStub: <_i3.HeatmapId, _i2.HeatmapController>{},
127+
) as Map<_i3.HeatmapId, _i2.HeatmapController>);
128+
129+
@override
130+
_i4.Map get googleMap => (super.noSuchMethod(
131+
Invocation.getter(#googleMap),
132+
returnValue: _i5.mapShim(),
133+
returnValueForMissingStub: _i5.mapShim(),
134+
) as _i4.Map);
135+
136+
@override
137+
set googleMap(_i4.Map? _googleMap) => super.noSuchMethod(
138+
Invocation.setter(
139+
#googleMap,
140+
_googleMap,
141+
),
142+
returnValueForMissingStub: null,
143+
);
144+
145+
@override
146+
int get mapId => (super.noSuchMethod(
147+
Invocation.getter(#mapId),
148+
returnValue: 0,
149+
returnValueForMissingStub: 0,
150+
) as int);
151+
152+
@override
153+
set mapId(int? _mapId) => super.noSuchMethod(
154+
Invocation.setter(
155+
#mapId,
156+
_mapId,
157+
),
158+
returnValueForMissingStub: null,
159+
);
160+
161+
@override
162+
void addHeatmaps(Set<_i3.Heatmap>? heatmapsToAdd) => super.noSuchMethod(
163+
Invocation.method(
164+
#addHeatmaps,
165+
[heatmapsToAdd],
166+
),
167+
returnValueForMissingStub: null,
168+
);
169+
170+
@override
171+
void changeHeatmaps(Set<_i3.Heatmap>? heatmapsToChange) => super.noSuchMethod(
172+
Invocation.method(
173+
#changeHeatmaps,
174+
[heatmapsToChange],
175+
),
176+
returnValueForMissingStub: null,
177+
);
178+
179+
@override
180+
void removeHeatmaps(Set<_i3.HeatmapId>? heatmapIdsToRemove) =>
181+
super.noSuchMethod(
182+
Invocation.method(
183+
#removeHeatmaps,
184+
[heatmapIdsToRemove],
185+
),
186+
returnValueForMissingStub: null,
187+
);
188+
189+
@override
190+
void bindToMap(
191+
int? mapId,
192+
_i4.Map? googleMap,
193+
) =>
194+
super.noSuchMethod(
195+
Invocation.method(
196+
#bindToMap,
197+
[
198+
mapId,
199+
googleMap,
200+
],
201+
),
202+
returnValueForMissingStub: null,
203+
);
204+
}
205+
117206
/// A class which mocks [PolygonsController].
118207
///
119208
/// See the documentation for Mockito's code generation for more information.

packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart

+10
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,16 @@ void main() {
263263

264264
verify(controller.updateCircles(expectedUpdates));
265265
});
266+
testWidgets('updateHeatmaps', (WidgetTester tester) async {
267+
final HeatmapUpdates expectedUpdates = HeatmapUpdates.from(
268+
const <Heatmap>{},
269+
const <Heatmap>{},
270+
);
271+
272+
await plugin.updateHeatmaps(expectedUpdates, mapId: mapId);
273+
274+
verify(controller.updateHeatmaps(expectedUpdates));
275+
});
266276
// Tile Overlays
267277
testWidgets('updateTileOverlays', (WidgetTester tester) async {
268278
final Set<TileOverlay> expectedOverlays = <TileOverlay>{

packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart

+11
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ class MockGoogleMapController extends _i1.Mock
137137
_i4.DebugSetOptionsFunction? setOptions,
138138
_i4.MarkersController? markers,
139139
_i4.CirclesController? circles,
140+
_i4.HeatmapsController? heatmaps,
140141
_i4.PolygonsController? polygons,
141142
_i4.PolylinesController? polylines,
142143
_i6.ClusterManagersController? clusterManagers,
@@ -151,6 +152,7 @@ class MockGoogleMapController extends _i1.Mock
151152
#setOptions: setOptions,
152153
#markers: markers,
153154
#circles: circles,
155+
#heatmaps: heatmaps,
154156
#polygons: polygons,
155157
#polylines: polylines,
156158
#clusterManagers: clusterManagers,
@@ -289,6 +291,15 @@ class MockGoogleMapController extends _i1.Mock
289291
returnValueForMissingStub: null,
290292
);
291293

294+
@override
295+
void updateHeatmaps(_i2.HeatmapUpdates? updates) => super.noSuchMethod(
296+
Invocation.method(
297+
#updateHeatmaps,
298+
[updates],
299+
),
300+
returnValueForMissingStub: null,
301+
);
302+
292303
@override
293304
void updatePolygons(_i2.PolygonUpdates? updates) => super.noSuchMethod(
294305
Invocation.method(

0 commit comments

Comments
 (0)