Skip to content
Open
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
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,36 @@
## [5.0.0-dev.1]
- FEATURE: Add maxClusterZoom option to prevent clusters from being formed
above a certain zoom.
- FEATURE: Added SuperclusterScope and SuperclusterState.of(context) methods.
This allows accessing of the supercluster layer state from children of the
relevant SuperclusterScope. For example if you wrap your Scaffold in
SuperclusterScope() and the scaffold contains a FlutterMap with a
SuperclusterLayer you will be able to access the state of the layer from
children of Scaffold.
- FEATURE: Added addAll() and removeAll() to SuperclusterMutableController for
efficiently adding/removing multiple markers at once.
- FEATURE: SuperclusterMutableController's add/remove methods now fully
recluster markers affected by an addition/removal.
- FEATURE: The indexBuilder option now defaults to building in the root index.
- FEATURE: Popups will now be hidden automatically when removing their Marker.
- FEATURE: Splayed clusters will now be collapsed automatically when removing
one of their points or inserting a point which causes the splay cluster to
change.
- FEATURE: flutter_map 6.0.0-dev.1.
- FEATURE: flutter_map_marker_popup v5.3.0-dev.1
- FEATURE: supercluster v3.0.0.
- DEPRECATION: SuperclusterLayer's anchor has been renamed to clusterAnchorPos.
- CHORE: Example app tidy-up. Added desktop platforms and renamed/simplified
examples.

Note that this version included major changes internally. I was close to
completing this verison before I noticed some issues with how FlutterMap works
which required a huge refactor of FlutterMap. That PR has taken quite some time
which put this version on hold. As a result I have come back to some incomplete
changes and there may be breaking changes or deprecations missing in the
CHANGELOG despite my best efforts to list them all. If you notice something
don't hesitate to open an issue.

## [4.3.0]

- FEATURE: flutter_map 5.0.0
Expand Down
10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
# Flutter Map Supercluster (previously `flutter_map_fast_cluster`)
# Flutter Map Supercluster

Two different Marker clustering layers for [flutter_map](https://github.com/fleaflet/flutter_map):

- `SuperclusterImmutableLayer`: An extremely fast Marker clustering layer, Markers may not be
- `SuperclusterLayer.immutable`: An extremely fast Marker clustering layer, Markers may not be
added/removed.
- `SuperclusterMutableLayer`: An slightly slower (but still very fast) Marker clustering layer.
- `SuperclusterLayer.mutable`: A slightly slower (but still very fast) Marker clustering layer.
Markers can be added/removed.

If you want beautiful clustering animations check out `flutter_map_marker_plugin`. It will perform
well for quite a lot of Markers on most devices. If you are running in to performance issues and are
happy to sacrifice animations then this package may be for you.
![Example](https://github.com/rorystephenson/project_gifs/blob/master/flutter_map_supercluster/demo.gif)

## Usage

Expand Down
41 changes: 38 additions & 3 deletions example/.metadata
Original file line number Diff line number Diff line change
@@ -1,10 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
# This file should be version controlled.

version:
revision: 824b1ad3f862d4c9661f7d1e601d8df8915400a6
channel: master
revision: 796c8ef79279f9c774545b3771238c3098dbefab
channel: stable

project_type: app

# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: android
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: ios
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: linux
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: macos
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: web
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: windows
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab

# User provided section

# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
3 changes: 1 addition & 2 deletions example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ android {
}

defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.flutter_map_supercluster_example"
applicationId "ng.balanci.flutter_map_supercluster_example"
minSdkVersion 16
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
Expand Down
2 changes: 1 addition & 1 deletion example/android/app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.flutter_map_supercluster_example">
package="ng.balanci.flutter_map_supercluster_example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
Expand Down
4 changes: 3 additions & 1 deletion example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.flutter_map_supercluster_example">
package="ng.balanci.flutter_map_supercluster_example">

<application
android:label="flutter_map_supercluster_example"
Expand Down Expand Up @@ -37,4 +37,6 @@
android:name="flutterEmbedding"
android:value="2" />
</application>

<uses-permission android:name="android.permission.INTERNET" />
</manifest>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.example.flutter_map_supercluster_example
package ng.balanci.flutter_map_supercluster_example

import io.flutter.embedding.android.FlutterActivity

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package ng.balanci.example

import io.flutter.embedding.android.FlutterActivity

class MainActivity: FlutterActivity() {
}
2 changes: 1 addition & 1 deletion example/android/app/src/profile/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.flutter_map_supercluster_example">
package="ng.balanci.flutter_map_supercluster_example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
Expand Down
2 changes: 1 addition & 1 deletion example/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
org.gradle.jvmargs=-Xmx1536M --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED
android.useAndroidX=true
android.enableJetifier=true
6 changes: 3 additions & 3 deletions example/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterMapMarkerClusterExample;
PRODUCT_BUNDLE_IDENTIFIER = ng.balanci.flutterMapMarkerClusterExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
Expand Down Expand Up @@ -415,7 +415,7 @@
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterMapMarkerClusterExample;
PRODUCT_BUNDLE_IDENTIFIER = ng.balanci.flutterMapMarkerClusterExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
Expand All @@ -434,7 +434,7 @@
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterMapMarkerClusterExample;
PRODUCT_BUNDLE_IDENTIFIER = ng.balanci.flutterMapMarkerClusterExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
Expand Down
12 changes: 12 additions & 0 deletions example/ios/RunnerTests/RunnerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest

class RunnerTests: XCTestCase {

func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}

}
70 changes: 70 additions & 0 deletions example/lib/basic_example_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_map/plugin_api.dart';
import 'package:flutter_map_supercluster/flutter_map_supercluster.dart';
import 'package:flutter_map_supercluster_example/drawer.dart';
import 'package:flutter_map_supercluster_example/main.dart';
import 'package:latlong2/latlong.dart';

class BasicExamplePage extends StatelessWidget {
static const String route = 'basicExamplePage';

static const _totalMarkers = 3000;
static final _random = Random(42);
static const _initialCenter = LatLng(42.0, 10.0);
static final _markers = List<Marker>.generate(
_totalMarkers,
(_) => Marker(
builder: (context) => const Icon(Icons.location_on),
point: LatLng(
_random.nextDouble() * 3 - 1.5 + _initialCenter.latitude,
_random.nextDouble() * 3 - 1.5 + _initialCenter.longitude,
),
),
);

const BasicExamplePage({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Basic Example')),
drawer: buildDrawer(context, route),
body: FlutterMap(
options: const MapOptions(
initialCenter: _initialCenter,
initialZoom: 8,
maxZoom: 19,
),
children: <Widget>[
TileLayer(
urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: const ['a', 'b', 'c'],
userAgentPackageName: tileLayerPackageName,
),
SuperclusterLayer.immutable(
initialMarkers: _markers,
indexBuilder: IndexBuilders.computeWithOriginalMarkers,
clusterWidgetSize: const Size(40, 40),
maxClusterRadius: 120,
clusterAnchorPos: const AnchorPos.align(AnchorAlign.center),
builder: (context, position, markerCount, extraClusterData) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
color: Colors.blue),
child: Center(
child: Text(
markerCount.toString(),
style: const TextStyle(color: Colors.white),
),
),
);
},
),
],
),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,56 +7,53 @@ import 'package:flutter_map_supercluster/flutter_map_supercluster.dart';
import 'package:flutter_map_supercluster_example/drawer.dart';
import 'package:latlong2/latlong.dart';

class TooCloseToUnclusterPage extends StatefulWidget {
static const String route = 'tooCloseToUnclusterPage';
class ClusterSplayingPage extends StatefulWidget {
static const String route = 'clusterSplayingPage';

const TooCloseToUnclusterPage({Key? key}) : super(key: key);
const ClusterSplayingPage({Key? key}) : super(key: key);

@override
State<TooCloseToUnclusterPage> createState() =>
_TooCloseToUnclusterPageState();
State<ClusterSplayingPage> createState() => _ClusterSplayingPageState();
}

class _TooCloseToUnclusterPageState extends State<TooCloseToUnclusterPage>
class _ClusterSplayingPageState extends State<ClusterSplayingPage>
with TickerProviderStateMixin {
late final SuperclusterImmutableController _superclusterController;
late final AnimatedMapController _animatedMapController;

static final points = [
const LatLng(51.4001, -0.08001),
const LatLng(51.4003, -0.08003),
const LatLng(51.4005, -0.08005),
const LatLng(51.4006, -0.08006),
const LatLng(51.4009, -0.08009),
const LatLng(51.5, -0.09),
const LatLng(51.5, -0.09),
const LatLng(51.5, -0.09),
const LatLng(51.5, -0.09),
const LatLng(51.5, -0.09),
const LatLng(51.59, -0.099),
static const points = [
LatLng(51.4001, -0.08001),
LatLng(51.4003, -0.08003),
LatLng(51.4005, -0.08005),
LatLng(51.4006, -0.08006),
LatLng(51.4009, -0.08009),
LatLng(51.5, -0.09),
LatLng(51.5, -0.09),
LatLng(51.5, -0.09),
LatLng(51.5, -0.09),
LatLng(51.5, -0.09),
LatLng(51.59, -0.099),
];
late List<Marker> markers;
static final List<Marker> markers = points
.map(
(point) => Marker(
anchorPos: const AnchorPos.align(AnchorAlign.top),
rotateAlignment: AnchorAlign.top.rotationAlignment,
height: 30,
width: 30,
point: point,
rotate: true,
builder: (ctx) => const Icon(Icons.pin_drop),
),
)
.toList();

@override
void initState() {
super.initState();

_superclusterController = SuperclusterImmutableController();
_animatedMapController = AnimatedMapController(vsync: this);

markers = points
.map(
(point) => Marker(
anchorPos: AnchorPos.align(AnchorAlign.top),
rotateAlignment: AnchorAlign.top.rotationAlignment,
height: 30,
width: 30,
point: point,
rotate: true,
builder: (ctx) => const Icon(Icons.pin_drop),
),
)
.toList();
}

@override
Expand All @@ -70,7 +67,7 @@ class _TooCloseToUnclusterPageState extends State<TooCloseToUnclusterPage>
Widget build(BuildContext context) {
return PopupScope(
child: Scaffold(
appBar: AppBar(title: const Text('Too close to uncluster page')),
appBar: AppBar(title: const Text('Cluster Splaying')),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Expand All @@ -87,13 +84,13 @@ class _TooCloseToUnclusterPageState extends State<TooCloseToUnclusterPage>
),
],
),
drawer: buildDrawer(context, TooCloseToUnclusterPage.route),
drawer: buildDrawer(context, ClusterSplayingPage.route),
body: FlutterMap(
mapController: _animatedMapController.mapController,
options: MapOptions(
center: const LatLng(51.4931, -0.1003),
zoom: 10,
maxZoom: 15,
initialCenter: const LatLng(51.4931, -0.1003),
initialZoom: 10,
maxZoom: 16,
onTap: (_, __) {
_superclusterController.collapseSplayedClusters();
},
Expand All @@ -112,7 +109,7 @@ class _TooCloseToUnclusterPageState extends State<TooCloseToUnclusterPage>
zoom: zoom,
),
clusterWidgetSize: const Size(40, 40),
anchor: AnchorPos.align(AnchorAlign.center),
clusterAnchorPos: const AnchorPos.align(AnchorAlign.center),
popupOptions: PopupOptions(
selectedMarkerBuilder: (context, marker) => const Icon(
Icons.pin_drop,
Expand Down
Loading