Skip to content

Commit

Permalink
[google_maps_flutter] Add heatmap support (#3257)
Browse files Browse the repository at this point in the history
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
```
  • Loading branch information
Rexios80 authored Aug 6, 2024
1 parent db8bfed commit acadb40
Show file tree
Hide file tree
Showing 12 changed files with 761 additions and 9 deletions.
4 changes: 4 additions & 0 deletions packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.8.0

* Adds support for heatmap layers.

## 2.7.1

* Updates the example app to use TLHC mode, per current package guidance.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;

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

void runTests() {
const double floatTolerance = 1e-8;

GoogleMapsFlutterPlatform.instance.enableDebugInspection();

final GoogleMapsInspectorPlatform inspector =
Expand Down Expand Up @@ -204,6 +207,271 @@ void runTests() {
},
);
}, skip: isWeb /* Tiles not supported on the web */);

/// Check that two lists of [WeightedLatLng] are more or less equal.
void expectHeatmapDataMoreOrLessEquals(
List<WeightedLatLng> data1,
List<WeightedLatLng> data2,
) {
expect(data1.length, data2.length);
for (int i = 0; i < data1.length; i++) {
final WeightedLatLng wll1 = data1[i];
final WeightedLatLng wll2 = data2[i];
expect(wll1.weight, wll2.weight);
expect(wll1.point.latitude, moreOrLessEquals(wll2.point.latitude));
expect(wll1.point.longitude, moreOrLessEquals(wll2.point.longitude));
}
}

/// Check that two [HeatmapGradient]s are more or less equal.
void expectHeatmapGradientMoreOrLessEquals(
HeatmapGradient? gradient1,
HeatmapGradient? gradient2,
) {
if (gradient1 == null || gradient2 == null) {
expect(gradient1, gradient2);
return;
}
expect(gradient2, isNotNull);

expect(gradient1.colors.length, gradient2.colors.length);
for (int i = 0; i < gradient1.colors.length; i++) {
final HeatmapGradientColor color1 = gradient1.colors[i];
final HeatmapGradientColor color2 = gradient2.colors[i];
expect(color1.color, color2.color);
expect(
color1.startPoint,
moreOrLessEquals(color2.startPoint, epsilon: floatTolerance),
);
}

expect(gradient1.colorMapSize, gradient2.colorMapSize);
}

void expectHeatmapEquals(Heatmap heatmap1, Heatmap heatmap2) {
expectHeatmapDataMoreOrLessEquals(heatmap1.data, heatmap2.data);
expectHeatmapGradientMoreOrLessEquals(heatmap1.gradient, heatmap2.gradient);

// Only Android supports `maxIntensity`
// so the platform value is undefined on others.
bool canHandleMaxIntensity() {
return Platform.isAndroid;
}

// Only iOS supports `minimumZoomIntensity` and `maximumZoomIntensity`
// so the platform value is undefined on others.
bool canHandleZoomIntensity() {
return Platform.isIOS;
}

if (canHandleMaxIntensity()) {
expect(heatmap1.maxIntensity, heatmap2.maxIntensity);
}
expect(
heatmap1.opacity,
moreOrLessEquals(heatmap2.opacity, epsilon: floatTolerance),
);
expect(heatmap1.radius, heatmap2.radius);
if (canHandleZoomIntensity()) {
expect(heatmap1.minimumZoomIntensity, heatmap2.minimumZoomIntensity);
expect(heatmap1.maximumZoomIntensity, heatmap2.maximumZoomIntensity);
}
}

const Heatmap heatmap1 = Heatmap(
heatmapId: HeatmapId('heatmap_1'),
data: <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), weight: 2)
],
dissipating: false,
gradient: HeatmapGradient(
<HeatmapGradientColor>[
HeatmapGradientColor(
Color.fromARGB(255, 0, 255, 255),
0.2,
),
HeatmapGradientColor(
Color.fromARGB(255, 0, 63, 255),
0.4,
),
HeatmapGradientColor(
Color.fromARGB(255, 0, 0, 191),
0.6,
),
HeatmapGradientColor(
Color.fromARGB(255, 63, 0, 91),
0.8,
),
HeatmapGradientColor(
Color.fromARGB(255, 255, 0, 0),
1,
),
],
),
maxIntensity: 1,
opacity: 0.5,
radius: HeatmapRadius.fromPixels(40),
minimumZoomIntensity: 1,
maximumZoomIntensity: 20,
);

testWidgets('set heatmap correctly', (WidgetTester tester) async {
final Completer<int> mapIdCompleter = Completer<int>();
final Heatmap heatmap2 = Heatmap(
heatmapId: const HeatmapId('heatmap_2'),
data: heatmap1.data,
dissipating: heatmap1.dissipating,
gradient: heatmap1.gradient,
maxIntensity: heatmap1.maxIntensity,
opacity: heatmap1.opacity - 0.1,
radius: heatmap1.radius,
minimumZoomIntensity: heatmap1.minimumZoomIntensity,
maximumZoomIntensity: heatmap1.maximumZoomIntensity,
);

await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GoogleMap(
initialCameraPosition: kInitialCameraPosition,
heatmaps: <Heatmap>{heatmap1, heatmap2},
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
),
);
await tester.pumpAndSettle(const Duration(seconds: 3));

final int mapId = await mapIdCompleter.future;
final GoogleMapsInspectorPlatform inspector =
GoogleMapsInspectorPlatform.instance!;

if (inspector.supportsGettingHeatmapInfo()) {
final Heatmap heatmapInfo1 =
(await inspector.getHeatmapInfo(heatmap1.mapsId, mapId: mapId))!;
final Heatmap heatmapInfo2 =
(await inspector.getHeatmapInfo(heatmap2.mapsId, mapId: mapId))!;

expectHeatmapEquals(heatmap1, heatmapInfo1);
expectHeatmapEquals(heatmap2, heatmapInfo2);
}
});

testWidgets('update heatmaps correctly', (WidgetTester tester) async {
final Completer<int> mapIdCompleter = Completer<int>();
final Key key = GlobalKey();

await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
heatmaps: <Heatmap>{heatmap1},
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
),
);

final int mapId = await mapIdCompleter.future;
final GoogleMapsInspectorPlatform inspector =
GoogleMapsInspectorPlatform.instance!;

final Heatmap heatmap1New = heatmap1.copyWith(
dataParam: heatmap1.data.sublist(5),
dissipatingParam: !heatmap1.dissipating,
gradientParam: heatmap1.gradient,
maxIntensityParam: heatmap1.maxIntensity! + 1,
opacityParam: heatmap1.opacity - 0.1,
radiusParam: HeatmapRadius.fromPixels(heatmap1.radius.radius + 1),
minimumZoomIntensityParam: heatmap1.minimumZoomIntensity + 1,
maximumZoomIntensityParam: heatmap1.maximumZoomIntensity + 1,
);

await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
heatmaps: <Heatmap>{heatmap1New},
onMapCreated: (GoogleMapController controller) {
fail('update: OnMapCreated should get called only once.');
},
),
),
);

await tester.pumpAndSettle(const Duration(seconds: 3));

if (inspector.supportsGettingHeatmapInfo()) {
final Heatmap heatmapInfo1 =
(await inspector.getHeatmapInfo(heatmap1.mapsId, mapId: mapId))!;

expectHeatmapEquals(heatmap1New, heatmapInfo1);
}
});

testWidgets('remove heatmaps correctly', (WidgetTester tester) async {
final Completer<int> mapIdCompleter = Completer<int>();
final Key key = GlobalKey();

await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
heatmaps: <Heatmap>{heatmap1},
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
),
);

final int mapId = await mapIdCompleter.future;
final GoogleMapsInspectorPlatform inspector =
GoogleMapsInspectorPlatform.instance!;

await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
fail('OnMapCreated should get called only once.');
},
),
),
);

await tester.pumpAndSettle(const Duration(seconds: 3));

if (inspector.supportsGettingHeatmapInfo()) {
final Heatmap? heatmapInfo1 =
await inspector.getHeatmapInfo(heatmap1.mapsId, mapId: mapId);

expect(heatmapInfo1, isNull);
}
});
}

class _DebugTileProvider implements TileProvider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,12 +263,12 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh",
"${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/GoogleMaps/GoogleMapsResources.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/google_maps_flutter_ios/google_maps_flutter_ios_privacy.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMaps.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMapsResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/google_maps_flutter_ios_privacy.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
Loading

0 comments on commit acadb40

Please sign in to comment.