Skip to content

Commit acadb40

Browse files
authored
[google_maps_flutter] Add heatmap support (#3257)
Transferred from flutter/plugins#5274 Adds heatmap support for web, iOS, and Android *List which issues are fixed by this PR. You must list at least one issue.* Fixes [#33586](flutter/flutter#33586) Fixes [#86811](flutter/flutter#86811) *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* # IF YOU WANT TO USE THIS NOW pubspec.yaml: ```yaml dependencies: google_maps_flutter: ^2.6.0 dependency_overrides: google_maps_flutter: git: url: https://github.com/Rexios80/packages_flutter ref: 01ab1db path: packages/google_maps_flutter/google_maps_flutter google_maps_flutter_web: git: url: https://github.com/Rexios80/packages_flutter ref: 01ab1db path: packages/google_maps_flutter/google_maps_flutter_web google_maps_flutter_android: git: url: https://github.com/Rexios80/packages_flutter ref: 01ab1db path: packages/google_maps_flutter/google_maps_flutter_android google_maps_flutter_ios: git: url: https://github.com/Rexios80/packages_flutter ref: 01ab1db path: packages/google_maps_flutter/google_maps_flutter_ios google_maps_flutter_platform_interface: git: url: https://github.com/Rexios80/packages_flutter ref: 01ab1db path: packages/google_maps_flutter/google_maps_flutter_platform_interface ```
1 parent db8bfed commit acadb40

File tree

12 files changed

+761
-9
lines changed

12 files changed

+761
-9
lines changed

packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md

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

37
* Updates the example app to use TLHC mode, per current package guidance.

packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/tiles_inspector.dart

+268
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
import 'dart:async';
6+
import 'dart:io';
67
import 'dart:ui' as ui;
78

89
import 'package:flutter/foundation.dart';
@@ -21,6 +22,8 @@ void main() {
2122
}
2223

2324
void runTests() {
25+
const double floatTolerance = 1e-8;
26+
2427
GoogleMapsFlutterPlatform.instance.enableDebugInspection();
2528

2629
final GoogleMapsInspectorPlatform inspector =
@@ -204,6 +207,271 @@ void runTests() {
204207
},
205208
);
206209
}, skip: isWeb /* Tiles not supported on the web */);
210+
211+
/// Check that two lists of [WeightedLatLng] are more or less equal.
212+
void expectHeatmapDataMoreOrLessEquals(
213+
List<WeightedLatLng> data1,
214+
List<WeightedLatLng> data2,
215+
) {
216+
expect(data1.length, data2.length);
217+
for (int i = 0; i < data1.length; i++) {
218+
final WeightedLatLng wll1 = data1[i];
219+
final WeightedLatLng wll2 = data2[i];
220+
expect(wll1.weight, wll2.weight);
221+
expect(wll1.point.latitude, moreOrLessEquals(wll2.point.latitude));
222+
expect(wll1.point.longitude, moreOrLessEquals(wll2.point.longitude));
223+
}
224+
}
225+
226+
/// Check that two [HeatmapGradient]s are more or less equal.
227+
void expectHeatmapGradientMoreOrLessEquals(
228+
HeatmapGradient? gradient1,
229+
HeatmapGradient? gradient2,
230+
) {
231+
if (gradient1 == null || gradient2 == null) {
232+
expect(gradient1, gradient2);
233+
return;
234+
}
235+
expect(gradient2, isNotNull);
236+
237+
expect(gradient1.colors.length, gradient2.colors.length);
238+
for (int i = 0; i < gradient1.colors.length; i++) {
239+
final HeatmapGradientColor color1 = gradient1.colors[i];
240+
final HeatmapGradientColor color2 = gradient2.colors[i];
241+
expect(color1.color, color2.color);
242+
expect(
243+
color1.startPoint,
244+
moreOrLessEquals(color2.startPoint, epsilon: floatTolerance),
245+
);
246+
}
247+
248+
expect(gradient1.colorMapSize, gradient2.colorMapSize);
249+
}
250+
251+
void expectHeatmapEquals(Heatmap heatmap1, Heatmap heatmap2) {
252+
expectHeatmapDataMoreOrLessEquals(heatmap1.data, heatmap2.data);
253+
expectHeatmapGradientMoreOrLessEquals(heatmap1.gradient, heatmap2.gradient);
254+
255+
// Only Android supports `maxIntensity`
256+
// so the platform value is undefined on others.
257+
bool canHandleMaxIntensity() {
258+
return Platform.isAndroid;
259+
}
260+
261+
// Only iOS supports `minimumZoomIntensity` and `maximumZoomIntensity`
262+
// so the platform value is undefined on others.
263+
bool canHandleZoomIntensity() {
264+
return Platform.isIOS;
265+
}
266+
267+
if (canHandleMaxIntensity()) {
268+
expect(heatmap1.maxIntensity, heatmap2.maxIntensity);
269+
}
270+
expect(
271+
heatmap1.opacity,
272+
moreOrLessEquals(heatmap2.opacity, epsilon: floatTolerance),
273+
);
274+
expect(heatmap1.radius, heatmap2.radius);
275+
if (canHandleZoomIntensity()) {
276+
expect(heatmap1.minimumZoomIntensity, heatmap2.minimumZoomIntensity);
277+
expect(heatmap1.maximumZoomIntensity, heatmap2.maximumZoomIntensity);
278+
}
279+
}
280+
281+
const Heatmap heatmap1 = Heatmap(
282+
heatmapId: HeatmapId('heatmap_1'),
283+
data: <WeightedLatLng>[
284+
WeightedLatLng(LatLng(37.782, -122.447)),
285+
WeightedLatLng(LatLng(37.782, -122.445)),
286+
WeightedLatLng(LatLng(37.782, -122.443)),
287+
WeightedLatLng(LatLng(37.782, -122.441)),
288+
WeightedLatLng(LatLng(37.782, -122.439)),
289+
WeightedLatLng(LatLng(37.782, -122.437)),
290+
WeightedLatLng(LatLng(37.782, -122.435)),
291+
WeightedLatLng(LatLng(37.785, -122.447)),
292+
WeightedLatLng(LatLng(37.785, -122.445)),
293+
WeightedLatLng(LatLng(37.785, -122.443)),
294+
WeightedLatLng(LatLng(37.785, -122.441)),
295+
WeightedLatLng(LatLng(37.785, -122.439)),
296+
WeightedLatLng(LatLng(37.785, -122.437)),
297+
WeightedLatLng(LatLng(37.785, -122.435), weight: 2)
298+
],
299+
dissipating: false,
300+
gradient: HeatmapGradient(
301+
<HeatmapGradientColor>[
302+
HeatmapGradientColor(
303+
Color.fromARGB(255, 0, 255, 255),
304+
0.2,
305+
),
306+
HeatmapGradientColor(
307+
Color.fromARGB(255, 0, 63, 255),
308+
0.4,
309+
),
310+
HeatmapGradientColor(
311+
Color.fromARGB(255, 0, 0, 191),
312+
0.6,
313+
),
314+
HeatmapGradientColor(
315+
Color.fromARGB(255, 63, 0, 91),
316+
0.8,
317+
),
318+
HeatmapGradientColor(
319+
Color.fromARGB(255, 255, 0, 0),
320+
1,
321+
),
322+
],
323+
),
324+
maxIntensity: 1,
325+
opacity: 0.5,
326+
radius: HeatmapRadius.fromPixels(40),
327+
minimumZoomIntensity: 1,
328+
maximumZoomIntensity: 20,
329+
);
330+
331+
testWidgets('set heatmap correctly', (WidgetTester tester) async {
332+
final Completer<int> mapIdCompleter = Completer<int>();
333+
final Heatmap heatmap2 = Heatmap(
334+
heatmapId: const HeatmapId('heatmap_2'),
335+
data: heatmap1.data,
336+
dissipating: heatmap1.dissipating,
337+
gradient: heatmap1.gradient,
338+
maxIntensity: heatmap1.maxIntensity,
339+
opacity: heatmap1.opacity - 0.1,
340+
radius: heatmap1.radius,
341+
minimumZoomIntensity: heatmap1.minimumZoomIntensity,
342+
maximumZoomIntensity: heatmap1.maximumZoomIntensity,
343+
);
344+
345+
await tester.pumpWidget(
346+
Directionality(
347+
textDirection: TextDirection.ltr,
348+
child: GoogleMap(
349+
initialCameraPosition: kInitialCameraPosition,
350+
heatmaps: <Heatmap>{heatmap1, heatmap2},
351+
onMapCreated: (GoogleMapController controller) {
352+
mapIdCompleter.complete(controller.mapId);
353+
},
354+
),
355+
),
356+
);
357+
await tester.pumpAndSettle(const Duration(seconds: 3));
358+
359+
final int mapId = await mapIdCompleter.future;
360+
final GoogleMapsInspectorPlatform inspector =
361+
GoogleMapsInspectorPlatform.instance!;
362+
363+
if (inspector.supportsGettingHeatmapInfo()) {
364+
final Heatmap heatmapInfo1 =
365+
(await inspector.getHeatmapInfo(heatmap1.mapsId, mapId: mapId))!;
366+
final Heatmap heatmapInfo2 =
367+
(await inspector.getHeatmapInfo(heatmap2.mapsId, mapId: mapId))!;
368+
369+
expectHeatmapEquals(heatmap1, heatmapInfo1);
370+
expectHeatmapEquals(heatmap2, heatmapInfo2);
371+
}
372+
});
373+
374+
testWidgets('update heatmaps correctly', (WidgetTester tester) async {
375+
final Completer<int> mapIdCompleter = Completer<int>();
376+
final Key key = GlobalKey();
377+
378+
await tester.pumpWidget(
379+
Directionality(
380+
textDirection: TextDirection.ltr,
381+
child: GoogleMap(
382+
key: key,
383+
initialCameraPosition: kInitialCameraPosition,
384+
heatmaps: <Heatmap>{heatmap1},
385+
onMapCreated: (GoogleMapController controller) {
386+
mapIdCompleter.complete(controller.mapId);
387+
},
388+
),
389+
),
390+
);
391+
392+
final int mapId = await mapIdCompleter.future;
393+
final GoogleMapsInspectorPlatform inspector =
394+
GoogleMapsInspectorPlatform.instance!;
395+
396+
final Heatmap heatmap1New = heatmap1.copyWith(
397+
dataParam: heatmap1.data.sublist(5),
398+
dissipatingParam: !heatmap1.dissipating,
399+
gradientParam: heatmap1.gradient,
400+
maxIntensityParam: heatmap1.maxIntensity! + 1,
401+
opacityParam: heatmap1.opacity - 0.1,
402+
radiusParam: HeatmapRadius.fromPixels(heatmap1.radius.radius + 1),
403+
minimumZoomIntensityParam: heatmap1.minimumZoomIntensity + 1,
404+
maximumZoomIntensityParam: heatmap1.maximumZoomIntensity + 1,
405+
);
406+
407+
await tester.pumpWidget(
408+
Directionality(
409+
textDirection: TextDirection.ltr,
410+
child: GoogleMap(
411+
key: key,
412+
initialCameraPosition: kInitialCameraPosition,
413+
heatmaps: <Heatmap>{heatmap1New},
414+
onMapCreated: (GoogleMapController controller) {
415+
fail('update: OnMapCreated should get called only once.');
416+
},
417+
),
418+
),
419+
);
420+
421+
await tester.pumpAndSettle(const Duration(seconds: 3));
422+
423+
if (inspector.supportsGettingHeatmapInfo()) {
424+
final Heatmap heatmapInfo1 =
425+
(await inspector.getHeatmapInfo(heatmap1.mapsId, mapId: mapId))!;
426+
427+
expectHeatmapEquals(heatmap1New, heatmapInfo1);
428+
}
429+
});
430+
431+
testWidgets('remove heatmaps correctly', (WidgetTester tester) async {
432+
final Completer<int> mapIdCompleter = Completer<int>();
433+
final Key key = GlobalKey();
434+
435+
await tester.pumpWidget(
436+
Directionality(
437+
textDirection: TextDirection.ltr,
438+
child: GoogleMap(
439+
key: key,
440+
initialCameraPosition: kInitialCameraPosition,
441+
heatmaps: <Heatmap>{heatmap1},
442+
onMapCreated: (GoogleMapController controller) {
443+
mapIdCompleter.complete(controller.mapId);
444+
},
445+
),
446+
),
447+
);
448+
449+
final int mapId = await mapIdCompleter.future;
450+
final GoogleMapsInspectorPlatform inspector =
451+
GoogleMapsInspectorPlatform.instance!;
452+
453+
await tester.pumpWidget(
454+
Directionality(
455+
textDirection: TextDirection.ltr,
456+
child: GoogleMap(
457+
key: key,
458+
initialCameraPosition: kInitialCameraPosition,
459+
onMapCreated: (GoogleMapController controller) {
460+
fail('OnMapCreated should get called only once.');
461+
},
462+
),
463+
),
464+
);
465+
466+
await tester.pumpAndSettle(const Duration(seconds: 3));
467+
468+
if (inspector.supportsGettingHeatmapInfo()) {
469+
final Heatmap? heatmapInfo1 =
470+
await inspector.getHeatmapInfo(heatmap1.mapsId, mapId: mapId);
471+
472+
expect(heatmapInfo1, isNull);
473+
}
474+
});
207475
}
208476

209477
class _DebugTileProvider implements TileProvider {

packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.pbxproj

+2-2
Original file line numberDiff line numberDiff line change
@@ -263,12 +263,12 @@
263263
);
264264
inputPaths = (
265265
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh",
266-
"${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle",
266+
"${PODS_CONFIGURATION_BUILD_DIR}/GoogleMaps/GoogleMapsResources.bundle",
267267
"${PODS_CONFIGURATION_BUILD_DIR}/google_maps_flutter_ios/google_maps_flutter_ios_privacy.bundle",
268268
);
269269
name = "[CP] Copy Pods Resources";
270270
outputPaths = (
271-
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMaps.bundle",
271+
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMapsResources.bundle",
272272
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/google_maps_flutter_ios_privacy.bundle",
273273
);
274274
runOnlyForDeploymentPostprocessing = 0;

0 commit comments

Comments
 (0)