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

chore: added visible region in change camera position callback for custom clusterization #16

Merged
merged 10 commits into from
Mar 18, 2024
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/surfstudio/yandex-mapkit-lite-flutter/assets/54618146/b1ca707b-b162-43d5-a9b1-d1895ab99e14">
<source media="(prefers-color-scheme: light)" srcset="https://github.com/surfstudio/yandex-mapkit-lite-flutter/assets/54618146/f88243c6-26f0-4491-93bd-baf4ad5a5e50">
<img alt="Shows an illustrated sun in light mode and a moon with stars in dark mode." src="https://github.com/surfstudio/yandex-mapkit-lite-flutter/assets/54618146/f88243c6-26f0-4491-93bd-baf4ad5a5e50">
</picture>
<p align="center">
<img src="https://raw.githubusercontent.com/surfstudio/yandex-mapkit-lite-flutter/main/assets/yandex_mapkit_lite.light.png" height="125" alt="yandex mapkit logo" />
</p>

[![Build Status](https://shields.io/github/actions/workflow/status/surfstudio/yandex-mapkit-lite-flutter/main.yml?logo=github&logoColor=white)](https://github.com/surfstudio/yandex-mapkit-lite-flutter)
[![Pub Version](https://img.shields.io/pub/v/yandex_mapkit_lite?logo=dart&logoColor=white)](https://pub.dev/packages/yandex_mapkit_lite)
Expand Down Expand Up @@ -145,6 +143,7 @@ For app bundle size optimization purposes, the original package was moved to lit
| Offline maps | :white_check_mark: | :white_check_mark: |
| Location manager | :white_check_mark: | :white_check_mark: |
| User location layer | :white_check_mark: | :white_check_mark: |
| Custom clusterization | :x: | :white_check_mark: - see the example project |
| Search, hints, geocoding | :white_check_mark: | :x: - consider using [yandex_geocoder](https://pub.dev/packages/yandex_geocoder) |
| Automobile, bicycle, and pedestrian routing | :white_check_mark: | :x: |
| Routing taking into account public transport | :white_check_mark: | :x: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,8 @@ public void onCameraPositionChanged(
arguments.put("cameraPosition", Utils.cameraPositionToJson(cameraPosition));
arguments.put("reason", cameraUpdateReason.ordinal());
arguments.put("finished", finished);

arguments.put("visibleRegion", Utils.visibleRegionToJson(map.getVisibleRegion()));

methodChannel.invokeMethod("onCameraPositionChanged", arguments);
}

Expand Down
Binary file removed assets/yandex_mapkit_lite.dark.png
Binary file not shown.
3 changes: 2 additions & 1 deletion example/.markdownlint.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"MD041": false,
"MD033": false,
"MD013": false
"MD013": false,
"MD034": false
}
8 changes: 3 additions & 5 deletions example/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/surfstudio/yandex-mapkit-lite-flutter/assets/54618146/b1ca707b-b162-43d5-a9b1-d1895ab99e14">
<source media="(prefers-color-scheme: light)" srcset="https://github.com/surfstudio/yandex-mapkit-lite-flutter/assets/54618146/f88243c6-26f0-4491-93bd-baf4ad5a5e50">
<img alt="Shows an illustrated sun in light mode and a moon with stars in dark mode." src="https://github.com/surfstudio/yandex-mapkit-lite-flutter/assets/54618146/f88243c6-26f0-4491-93bd-baf4ad5a5e50">
</picture>
<p align="center">
<img src="https://raw.githubusercontent.com/surfstudio/yandex-mapkit-lite-flutter/main/assets/yandex_mapkit_lite.light.png" height="125" alt="yandex mapkit logo" />
</p>

[![Build Status](https://shields.io/github/actions/workflow/status/surfstudio/yandex-mapkit-lite-flutter/main.yml?logo=github&logoColor=white)](https://github.com/surfstudio/yandex-mapkit-lite-flutter)
[![Coverage Status](https://img.shields.io/codecov/c/github/surfstudio/yandex-mapkit-lite-flutter?logo=codecov&logoColor=white)](https://app.codecov.io/gh/surfstudio/yandex-mapkit-lite-flutter)
Expand Down
16 changes: 15 additions & 1 deletion example/lib/presentation/screens/root_screen.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:yandex_mapkit_example/assets/res/assets.dart';
import 'package:yandex_mapkit_example/presentation/state/collection_map_strategy.dart';
import 'package:yandex_mapkit_example/presentation/state/custom_clusterization_map_strategy.dart';
import 'package:yandex_mapkit_example/presentation/state/custom_shape_map_strategy.dart';
import 'package:yandex_mapkit_example/presentation/state/default_map_strategy.dart';
import 'package:yandex_mapkit_example/presentation/state/draggable_placemark_map_strategy.dart';
Expand Down Expand Up @@ -85,13 +86,21 @@ class _RootScreenState extends State<RootScreen> {
///
/// See [CustomShapeMapStrategyDelegate] for more details.
CustomShapeMapStrategyDelegate(),

/// Strategy for map feature showcase with custom clusterization.
///
/// This strategy is showing how to add a custom clusterization to the map and
/// how to handle user interactions with the clusterization.
///
/// See [CustomClusterizationMapStrategyDelegate] for more details.
CustomClusterizationMapStrategyDelegate(),
];

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Yandex Mapkit Lite'),
title: Text(_mapStrategy.title),
actions: [
DropdownButton<int>(
value: _selectedStrategy,
Expand Down Expand Up @@ -120,6 +129,10 @@ class _RootScreenState extends State<RootScreen> {
value: 5,
child: Icon(Icons.shape_line),
),
DropdownMenuItem(
value: 6,
child: Icon(Icons.dashboard_customize),
),
],
onChanged: (value) {
setState(() {
Expand Down Expand Up @@ -159,6 +172,7 @@ class _RootScreenState extends State<RootScreen> {
},
onTrafficChanged: _mapStrategy.onTrafficLevelChanged,
onUserLocationUpdated: _mapStrategy.onUserLayer,
onCameraPositionChanged: _mapStrategy.onCameraPositionChanged,
allowUserInteractions: _mapStrategy.allowUserInteractions,
),
),
Expand Down
3 changes: 3 additions & 0 deletions example/lib/presentation/state/collection_map_strategy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,7 @@ class ClusterMapStrategyDelegate extends MapStrategyDelegate {
..._selectedPlaces.map(_createSelectedPlacemark),
};
}

@override
String get title => "Map objects clusterization";
}
227 changes: 227 additions & 0 deletions example/lib/presentation/state/custom_clusterization_map_strategy.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import 'package:fluster/fluster.dart';
import 'package:flutter/material.dart';
import 'package:yandex_mapkit_example/assets/res/assets.dart';
import 'package:yandex_mapkit_example/presentation/state/map_strategy.dart';
import 'package:yandex_mapkit_lite/yandex_mapkit_lite.dart';

class CustomClusterizationMapStrategyDelegate extends MapStrategyDelegate {
@override
Widget buildActions(BuildContext context) => const SizedBox();

/// There is only one placemark on the map.
///
/// The placemark is a circle with a red number inside, which is incremented
/// every time the user taps on the placemark.
///
/// There is also a action button for incrementing.
@override
Set<MapObject> get mapObjects {
return _clusteredObjects
.where((element) => element.location != null)
.map((cluster) {
if (cluster.isCluster ?? false) {
return PlacemarkMapObject(
point: cluster.location!,
icon: PlacemarkIcon.single(
PlacemarkIconStyle(
image: BitmapDescriptor.fromAssetImage(Assets.cluster),
),
),
consumeTapEvents: true,
text: PlacemarkText(
text: cluster.pointsSize.toString(),
style: const PlacemarkTextStyle(
color: Colors.black,
size: 10,
),
),
opacity: 1,
mapId: MapObjectId(cluster.clusterId!.toString()),
);
}

final id = int.tryParse(cluster.markerId ?? '');

return PlacemarkMapObject(
point: cluster.location!,
icon: PlacemarkIcon.single(
PlacemarkIconStyle(
image: BitmapDescriptor.fromAssetImage(
_selectedIds.contains(id) ? Assets.routeStart : Assets.routeEnd,
),
),
),
consumeTapEvents: true,
opacity: 1,
mapId: MapObjectId(cluster.markerId!),
onTap: (_, __) {
if (id == null) return;

if (_selectedIds.contains(id)) {
_selectedIds.remove(id);
} else {
_selectedIds.add(id);
}

notifyListeners();
},
);
}).toSet();
}

@override
String get title => "Custom clusterization";

late final List<ClusterableWrapper> _clusterables;

late final Fluster<ClusterableWrapper> _fluster;

CustomClusterizationMapStrategyDelegate() {
/// Generate clusterables objects,
/// which are used for Fluster tp
/// generate clusters.
_clusterables = _generateClusterables();

/// Create Fluster instance.
///
/// Fluster is a helper class to generate
/// clusters for the map.
///
/// It uses a quadtree data structure to
/// efficiently cluster large amounts of
/// points.
_fluster = Fluster<ClusterableWrapper>(
/// Any zoom value below minZoom will not generate clusters.
minZoom: 0,

/// Any zoom value above maxZoom will not generate clusters.
maxZoom: 20,

/// Cluster radius in pixels.
radius: 150,

/// Adjust the extent by powers of 2 (e.g. 512. 1024, ... max 8192) to get the
/// desired distance between markers where they start to cluster.
extent: 2048,

/// The size of the KD-tree leaf node, which affects performance.
nodeSize: 64,
points: _clusterables,
// ignore: avoid_types_on_closure_parameters
createCluster:
(BaseCluster? cluster, double? longitude, double? latitude) {
return ClusterableWrapper(
latitude: latitude,
longitude: longitude,
isCluster: true,
clusterId: cluster?.id,
pointsSize: cluster?.pointsSize,
childMarkerId: cluster?.childMarkerId,
);
},
);
}

List<ClusterableWrapper> _generateClusterables() => List.generate(
100,
(i) => ClusterableWrapper(
latitude: Constants.defaultLocation.latitude + (0.1 * (i - 50)),
longitude: Constants.defaultLocation.longitude + (0.1 * (i - 50)),
isCluster: false,
clusterId: 0,
pointsSize: 0,
markerId: '$i',
),
);

/// List of clustered objects.
///
/// This list is updated every time the camera position changes.
List<ClusterableWrapper> _clusteredObjects = [];

/// Set of selected marker ids.
///
/// This set is updated every time the user taps on a marker.
///
/// Demonstrates, how efficient can custom clusterization be
/// in comparison with out-of-the-box clusterization.
final _selectedIds = <int>{};

/// View expand factor.
///
/// This factor is used to expand the visible region
/// to make sure that all the markers are visible
/// and clusters are generated with markers
/// which are slightly outside the visible region.
static const _viewExpandFactor = 0;

@override
void onCameraPositionChanged(
CameraPosition cameraPosition,
CameraUpdateReason reason,
bool finished,
VisibleRegion visibleRegion,
) {
final zoom = cameraPosition.zoom;

_clusteredObjects = _fluster.clusters(
[
visibleRegion.bottomLeft.longitude - _viewExpandFactor,
visibleRegion.bottomLeft.latitude - _viewExpandFactor,
visibleRegion.topRight.longitude + _viewExpandFactor,
visibleRegion.topRight.latitude + _viewExpandFactor,
],
zoom.toInt(),
);

notifyListeners();
}
}

/// Wrapper for clusterable object.
///
/// This wrapper is used to demonstrate how to
/// use custom clusterization with Fluster.
///
/// See [Fluster] for more details.
class ClusterableWrapper implements Clusterable {
@override
String? childMarkerId;

@override
int? clusterId;

@override
bool? isCluster;

@override
double? latitude;

@override
double? longitude;

@override
String? markerId;

@override
int? pointsSize;

ClusterableWrapper({
this.latitude,
this.longitude,
this.isCluster,
this.clusterId,
this.pointsSize,
this.markerId,
this.childMarkerId,
});

Point? get location {
if (latitude == null || longitude == null) return null;

return Point(
latitude: latitude!,
longitude: longitude!,
);
}
}
3 changes: 3 additions & 0 deletions example/lib/presentation/state/custom_shape_map_strategy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,7 @@ class CustomShapeMapStrategyDelegate extends MapStrategyDelegate {
),
),
};

@override
String get title => "Custom map shapess";
}
3 changes: 3 additions & 0 deletions example/lib/presentation/state/default_map_strategy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,7 @@ class DefaultIncrementMapStrategyDelegate extends MapStrategyDelegate {
),
),
};

@override
String get title => "Yandex Map Lite Overview";
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,7 @@ class DraggablePlacemarkMapStrategyDelegate extends MapStrategyDelegate {

@override
bool get allowUserInteractions => false;

@override
String get title => "Draggable placemark";
}
15 changes: 15 additions & 0 deletions example/lib/presentation/state/map_strategy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ abstract class MapStrategyDelegate extends ChangeNotifier {
/// Set of map objects to be displayed on the map.
Set<MapObject> get mapObjects;

/// The title of the map strategy.
String get title;

/// Widget to be displayed as the floating action button on the screen.
///
/// This widget is used to provide user interactions with the map objects.
Expand All @@ -36,6 +39,18 @@ abstract class MapStrategyDelegate extends ChangeNotifier {
/// The implementation of this callback is optional.
Future<UserLocationView>? onUserLayer(UserLocationView view) => null;

/// Callback for user camera position change.
///
/// Callback is called whenever user camera position on map changes.
///
/// Allows us to introduce custom clusters on map.
void onCameraPositionChanged(
CameraPosition cameraPosition,
CameraUpdateReason reason,
bool finished,
VisibleRegion visibleRegion,
) {}

/// Map controller.
///
/// This controller is used to interact with the map.
Expand Down
3 changes: 3 additions & 0 deletions example/lib/presentation/state/traffic_map_strategy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,7 @@ class TrafficMapStrategyDelegate extends MapStrategyDelegate {

@override
bool get allowUserInteractions => false;

@override
String get title => "Traffic level layer";
}
Loading
Loading