Skip to content
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

[google_maps_flutter] Add support for marker clustering - platform interface #6158

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
Expand Up @@ -64,3 +64,4 @@ Aleksandr Yurkovskiy <sanekyy@gmail.com>
Anton Borries <mail@antonborri.es>
Alex Li <google@alexv525.com>
Rahul Raj <64.rahulraj@gmail.com>
Joonas Kerttula <joonas.kerttula@codemate.com>
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.6.0

* Adds support for marker clustering.

## 2.5.0

* Adds `style` to the `MapConfiguration` to allow setting style as part of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,12 @@ class MapLongPressEvent extends _PositionedMapEvent<void> {
/// The `position` of this event is the LatLng where the Map was long pressed.
MapLongPressEvent(int mapId, LatLng position) : super(mapId, position, null);
}

/// An event fired when a cluster icon managed by [ClusterManager] is tapped.
class ClusterTapEvent extends MapEvent<Cluster> {
/// Build a ClusterTapEvent Event triggered from the map represented by `mapId`.
///
/// The `value` of this event is a [Cluster] object that represents the tapped
/// cluster icon managed by [ClusterManager].
ClusterTapEvent(super.mapId, super.cluster);
}
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform {
return _events(mapId).whereType<MapLongPressEvent>();
}

@override
Stream<ClusterTapEvent> onClusterTap({required int mapId}) {
return _events(mapId).whereType<ClusterTapEvent>();
}

Future<dynamic> _handleMethodCall(MethodCall call, int mapId) async {
switch (call.method) {
case 'camera#onMoveStarted':
Expand Down Expand Up @@ -258,6 +263,36 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform {
arguments['zoom'] as int?,
);
return tile.toJson();
case 'cluster#onTap':
final Map<String, Object?> arguments = _getArgumentDictionary(call);
final ClusterManagerId clusterManagerId =
ClusterManagerId(arguments['clusterManagerId']! as String);
final LatLng position = LatLng.fromJson(arguments['position'])!;

final Map<String, List<dynamic>> latLngData =
(arguments['bounds']! as Map<dynamic, dynamic>).map(
(dynamic key, dynamic object) =>
MapEntry<String, List<dynamic>>(
key as String, object as List<dynamic>));

final LatLngBounds bounds = LatLngBounds(
northeast: LatLng.fromJson(latLngData['northeast'])!,
southwest: LatLng.fromJson(latLngData['southwest'])!);

final List<MarkerId> markerIds =
(arguments['markerIds']! as List<dynamic>)
.map((dynamic markerId) => MarkerId(markerId as String))
.toList();

_mapEventStreamController.add(ClusterTapEvent(
mapId,
Cluster(
clusterManagerId,
markerIds,
position: position,
bounds: bounds,
),
));
default:
throw MissingPluginException();
}
Expand Down Expand Up @@ -347,6 +382,17 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform {
);
}

@override
Future<void> updateClusterManagers(
ClusterManagerUpdates clusterManagerUpdates, {
required int mapId,
}) {
return channel(mapId).invokeMethod<void>(
'clusterManagers#update',
clusterManagerUpdates.toJson(),
);
}

@override
Future<void> clearTileCache(
TileOverlayId tileOverlayId, {
Expand Down Expand Up @@ -585,6 +631,7 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform {
Set<Polyline> polylines = const <Polyline>{},
Set<Circle> circles = const <Circle>{},
Set<TileOverlay> tileOverlays = const <TileOverlay>{},
Set<ClusterManager> clusterManagers = const <ClusterManager>{},
Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers,
Map<String, dynamic> mapOptions = const <String, dynamic>{},
}) {
Expand All @@ -599,6 +646,7 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform {
polygons: polygons,
polylines: polylines,
circles: circles,
clusterManagers: clusterManagers,
tileOverlays: tileOverlays),
mapOptions: mapOptions,
);
Expand All @@ -614,6 +662,7 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform {
Set<Polyline> polylines = const <Polyline>{},
Set<Circle> circles = const <Circle>{},
Set<TileOverlay> tileOverlays = const <TileOverlay>{},
Set<ClusterManager> clusterManagers = const <ClusterManager>{},
Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers,
Map<String, dynamic> mapOptions = const <String, dynamic>{},
}) {
Expand All @@ -627,6 +676,7 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform {
polylines: polylines,
circles: circles,
tileOverlays: tileOverlays,
clusterManagers: clusterManagers,
gestureRecognizers: gestureRecognizers,
mapOptions: mapOptions,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,20 @@ abstract class GoogleMapsFlutterPlatform extends PlatformInterface {
throw UnimplementedError('updateTileOverlays() has not been implemented.');
}

/// Updates cluster manager configuration.
///
/// Change listeners are notified once the update has been made on the
/// platform side.
///
/// The returned [Future] completes after listeners have been notified.
Future<void> updateClusterManagers(
ClusterManagerUpdates clusterManagerUpdates, {
required int mapId,
}) {
throw UnimplementedError(
'updateClusterManagers() has not been implemented.');
}

/// Clears the tile cache so that all tiles will be requested again from the
/// [TileProvider].
///
Expand Down Expand Up @@ -357,6 +371,11 @@ abstract class GoogleMapsFlutterPlatform extends PlatformInterface {
throw UnimplementedError('onLongPress() has not been implemented.');
}

/// A marker icon managed by [ClusterManager] has been tapped.
Stream<ClusterTapEvent> onClusterTap({required int mapId}) {
throw UnimplementedError('onClusterTap() has not been implemented.');
}

/// Dispose of whatever resources the `mapId` is holding on to.
void dispose({required int mapId}) {
throw UnimplementedError('dispose() has not been implemented.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,10 @@ abstract class GoogleMapsInspectorPlatform extends PlatformInterface {
{required int mapId}) {
throw UnimplementedError('getTileOverlayInfo() has not been implemented.');
}

/// Returns current clusters from [ClusterManager].
Future<List<Cluster>> getClusters(
{required int mapId, required ClusterManagerId clusterManagerId}) {
throw UnimplementedError('getClusters() has not been implemented.');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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.

import 'package:flutter/foundation.dart'
show immutable, listEquals, objectRuntimeType;
import 'types.dart';

/// A cluster containing multiple markers.
@immutable
class Cluster {
/// Creates a cluster with its location [LatLng], bounds [LatLngBounds],
/// and list of [MarkerId]s in the cluster.
const Cluster(
this.clusterManagerId,
this.markerIds, {
required this.position,
required this.bounds,
}) : assert(markerIds.length > 0);

/// ID of the [ClusterManager] of the cluster.
final ClusterManagerId clusterManagerId;

/// Cluster marker location.
final LatLng position;

/// The bounds containing all cluster markers.
final LatLngBounds bounds;

/// List of [MarkerId]s in the cluster.
final List<MarkerId> markerIds;

/// Returns the number of markers in the cluster.
int get count => markerIds.length;

@override
String toString() =>
'${objectRuntimeType(this, 'Cluster')}($clusterManagerId, $position, $bounds, $markerIds)';

@override
bool operator ==(Object other) {
return other is Cluster &&
other.clusterManagerId == clusterManagerId &&
other.position == position &&
other.bounds == bounds &&
listEquals(other.markerIds, markerIds);
}

@override
int get hashCode =>
Object.hash(clusterManagerId, position, bounds, markerIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// 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.

import 'package:flutter/foundation.dart' show immutable;
import 'types.dart';

/// Uniquely identifies a [ClusterManager] among [GoogleMap] clusters.
///
/// This does not have to be globally unique, only unique among the list.
@immutable
class ClusterManagerId extends MapsObjectId<ClusterManager> {
/// Creates an immutable identifier for a [ClusterManager].
const ClusterManagerId(super.value);
}

/// [ClusterManager] manages marker clustering for set of [Marker]s that have
/// the same [ClusterManagerId] set.
@immutable
class ClusterManager implements MapsObject<ClusterManager> {
/// Creates an immutable object for managing clustering for set of markers.
const ClusterManager({
required this.clusterManagerId,
this.onClusterTap,
});

/// Uniquely identifies a [ClusterManager].
final ClusterManagerId clusterManagerId;

@override
ClusterManagerId get mapsId => clusterManagerId;

/// Callback to receive tap events for cluster markers placed on this map.
final ArgumentCallback<Cluster>? onClusterTap;

/// Creates a new [ClusterManager] object whose values are the same as this instance,
/// unless overwritten by the specified parameters.
ClusterManager copyWith({
ArgumentCallback<Cluster>? onClusterTapParam,
}) {
return ClusterManager(
clusterManagerId: clusterManagerId,
onClusterTap: onClusterTapParam ?? onClusterTap,
);
}

/// Creates a new [ClusterManager] object whose values are the same as this instance.
@override
ClusterManager clone() => copyWith();

/// Converts this object to something serializable in JSON.
@override
Object toJson() {
final Map<String, Object> json = <String, Object>{};

void addIfPresent(String fieldName, Object? value) {
if (value != null) {
json[fieldName] = value;
}
}

addIfPresent('clusterManagerId', clusterManagerId.value);
return json;
}

@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is ClusterManager &&
clusterManagerId == other.clusterManagerId;
}

@override
int get hashCode => clusterManagerId.hashCode;

@override
String toString() {
return 'Cluster{clusterManagerId: $clusterManagerId, onClusterTap: $onClusterTap}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// 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.

import 'types.dart';

/// [ClusterManager] update events to be applied to the [GoogleMap].
///
/// Used in [GoogleMapController] when the map is updated.
// (Do not re-export)
class ClusterManagerUpdates extends MapsObjectUpdates<ClusterManager> {
/// Computes [ClusterManagerUpdates] given previous and current [ClusterManager]s.
ClusterManagerUpdates.from(super.previous, super.current)
: super.from(objectName: 'clusterManager');

/// Set of Clusters to be added in this update.
Set<ClusterManager> get clusterManagersToAdd => objectsToAdd;

/// Set of ClusterManagerIds to be removed in this update.
Set<ClusterManagerId> get clusterManagerIdsToRemove =>
objectIdsToRemove.cast<ClusterManagerId>();

/// Set of Clusters to be changed in this update.
Set<ClusterManager> get clusterManagersToChange => objectsToChange;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ class MapObjects {
this.polylines = const <Polyline>{},
this.circles = const <Circle>{},
this.tileOverlays = const <TileOverlay>{},
this.clusterManagers = const <ClusterManager>{},
});

final Set<Marker> markers;
final Set<Polygon> polygons;
final Set<Polyline> polylines;
final Set<Circle> circles;
final Set<TileOverlay> tileOverlays;
final Set<ClusterManager> clusterManagers;
}
Loading