Skip to content

[google_maps_flutter_web] Web changes to support heatmaps #7315

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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`.
Expand Down
13 changes: 13 additions & 0 deletions packages/google_maps_flutter/google_maps_flutter_web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ If you need marker clustering support, modify the <head> tag to load the [js-mar
</head>
```

## 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:
Expand All @@ -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 |
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ gmaps.Map mapShim() => throw UnimplementedError();
MockSpec<CirclesController>(
fallbackGenerators: <Symbol, Function>{#googleMap: mapShim},
),
MockSpec<HeatmapsController>(
fallbackGenerators: <Symbol, Function>{#googleMap: mapShim},
),
MockSpec<PolygonsController>(
fallbackGenerators: <Symbol, Function>{#googleMap: mapShim},
),
Expand Down Expand Up @@ -161,6 +164,20 @@ void main() {
}, throwsAssertionError);
});

testWidgets('cannot updateHeatmaps after dispose',
(WidgetTester tester) async {
controller.dispose();

expect(() {
controller.updateHeatmaps(
HeatmapUpdates.from(
const <Heatmap>{},
const <Heatmap>{},
),
);
}, throwsAssertionError);
});

testWidgets('cannot updatePolygons after dispose',
(WidgetTester tester) async {
controller.dispose();
Expand Down Expand Up @@ -229,6 +246,7 @@ void main() {

group('init', () {
late MockCirclesController circles;
late MockHeatmapsController heatmaps;
late MockMarkersController markers;
late MockPolygonsController polygons;
late MockPolylinesController polylines;
Expand All @@ -237,6 +255,7 @@ void main() {

setUp(() {
circles = MockCirclesController();
heatmaps = MockHeatmapsController();
markers = MockMarkersController();
polygons = MockPolygonsController();
polylines = MockPolylinesController();
Expand All @@ -249,6 +268,7 @@ void main() {
..debugSetOverrides(
createMap: (_, __) => map,
circles: circles,
heatmaps: heatmaps,
markers: markers,
polygons: polygons,
polylines: polylines,
Expand Down Expand Up @@ -287,6 +307,7 @@ void main() {
..debugSetOverrides(
createMap: (_, __) => map,
circles: circles,
heatmaps: heatmaps,
markers: markers,
polygons: polygons,
polylines: polylines,
Expand All @@ -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));
Expand All @@ -307,6 +329,17 @@ void main() {
circleId: CircleId('circle-1'),
zIndex: 1234,
),
}, heatmaps: <Heatmap>{
const Heatmap(
heatmapId: HeatmapId('heatmap-1'),
data: <WeightedLatLng>[
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: <Marker>{
const Marker(
markerId: MarkerId('marker-1'),
Expand Down Expand Up @@ -352,6 +385,7 @@ void main() {
controller = createController(mapObjects: mapObjects)
..debugSetOverrides(
circles: circles,
heatmaps: heatmaps,
markers: markers,
polygons: polygons,
polylines: polylines,
Expand All @@ -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));
Expand Down Expand Up @@ -670,6 +705,76 @@ void main() {
}));
});

testWidgets('updateHeatmaps', (WidgetTester tester) async {
final MockHeatmapsController mock = MockHeatmapsController();
controller.debugSetOverrides(heatmaps: mock);

const List<WeightedLatLng> heatmapPoints = <WeightedLatLng>[
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<Heatmap> previous = <Heatmap>{
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<Heatmap> current = <Heatmap>{
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(<HeatmapId>{
const HeatmapId('to-be-removed'),
}));
verify(mock.addHeatmaps(<Heatmap>{
const Heatmap(
heatmapId: HeatmapId('to-be-added'),
data: heatmapPoints,
radius: HeatmapRadius.fromPixels(20),
),
}));
verify(mock.changeHeatmaps(<Heatmap>{
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,16 @@ void main() {

verify(controller.updateCircles(expectedUpdates));
});
testWidgets('updateHeatmaps', (WidgetTester tester) async {
final HeatmapUpdates expectedUpdates = HeatmapUpdates.from(
const <Heatmap>{},
const <Heatmap>{},
);

await plugin.updateHeatmaps(expectedUpdates, mapId: mapId);

verify(controller.updateHeatmaps(expectedUpdates));
});
// Tile Overlays
testWidgets('updateTileOverlays', (WidgetTester tester) async {
final Set<TileOverlay> expectedOverlays = <TileOverlay>{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -151,6 +152,7 @@ class MockGoogleMapController extends _i1.Mock
#setOptions: setOptions,
#markers: markers,
#circles: circles,
#heatmaps: heatmaps,
#polygons: polygons,
#polylines: polylines,
#clusterManagers: clusterManagers,
Expand Down Expand Up @@ -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(
Expand Down
Loading