Comments (42)
With help of stephenmeyer's hack I implemented my version of viewport only clustering which is lightning-fast.
Here is a detailed description of all my changes.
Because my time was limited and I had to reuse the viewport bounds I created a tiny class to store the value in. Quick & dirty but it doesn't hurt anyone.
So, create a class in android/clustering. I called mine bvb.java and this is the content:
package com.google.maps.android.clustering;
import com.google.android.gms.maps.model.LatLngBounds;
public class bvb {
public static LatLngBounds mapClusterViewportBounds;
public static boolean infoWindowShowing = false;
}
This holds 2 variables that can be accessed anywhere within the clusterManager.
The mapClusterViewportBounds variable will contain the coordinates of the part in the map that is visible to the user.
The infoWindowShowing variable should be set to true if you are using an infowindow popup when a user clicks on a marker. In the usual code, the clustering is only recalculated when the zoomlevel changes. If a user clicks on a marker, it's centered and the map doesn't zoom. The infowindow shows and stays in place. But with viewport clustering implemented, panning should also recalculate clusters or else you'd not see anything until you zoom. If a marker is now clicked, the infowindow shows, the marker is centered which now triggers reclustering and the infowindow is removed again.
To prevent this, whenever you are showing an infoWindow, add bvb.infoWindowShowing = true;
When an infoWindow is closed, add bvb.infoWindowShowing = false;
Note that you should also add bvb.infoWindowShowing = false; whenever you load new data on the map as the infoWindow is also removed when the map is cleared.
Now edit ClusterManager.java
In the import section add the following:
import android.util.Log;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
Find the onCameraChange method and change it to the following:
@Override
public void onCameraChange(CameraPosition cameraPosition) {
// Added this to store the viewport bounds and increase slightly for user convenience
LatLngBounds b = mMap.getProjection().getVisibleRegion().latLngBounds;
LatLng bNE = b.northeast;
LatLng bSW = b.southwest;
double bAdjustLat = (bNE.latitude - bSW.latitude) * 0.15; // 15% increase
double bAdjustLon = (bNE.longitude - bSW.longitude) * 0.15; // 15% increase
bNE = new LatLng(bNE.latitude + bAdjustLat, bNE.longitude + bAdjustLon);
bSW = new LatLng(bSW.latitude - bAdjustLat, bSW.longitude - bAdjustLon);
bvb.mapClusterViewportBounds = new LatLngBounds(bSW, bNE);
if (mRenderer instanceof GoogleMap.OnCameraChangeListener) {
((GoogleMap.OnCameraChangeListener) mRenderer).onCameraChange(cameraPosition);
}
if (bvb.infoWindowShowing) {
Log.i("Cluster", "Not reclustering as we have an infoWindow");
return;
}
cluster();
}
We first load the viewport bounds into a variable. I increased the viewport with 15% so that the corners also show markers/clusters and panning shows at least some data to the sides. Just 15% extra won't hurt performance. You can play with this number and increase/decrease it to your liking.
Then I check if there's an infoWindow set. If so, it will not recluster until it's closed and the onCameraChange method is called.
Now edit algo/NonHierarchicalDistanceBasedAlgorithm.java.
In the import section add the following:
import android.util.Log;
import com.google.maps.android.clustering.bvb;
In the getClusters method find the line "for (QuadItem candidate : mItems) {
Underneath that is an if section about visitedCandidates.contains(candidate)).
Below that if-section, add this following code (for me it's on line 99).
if (bvb.mapClusterViewportBounds != null && !bvb.mapClusterViewportBounds.contains(candidate.mPosition)) {
continue; // Added to exclude markers outside viewport
}
While iterating over all map-points, this doesn't process any point outside your viewport + the 15%.
Which is what make the clustering perform quite well.
Then edit algo/PreCachingAlgorithmDecorator.java.
Find the getClustersInternal method.
Uncomment the following line:
mCache.put(discreteZoom, results);
The cluster-results are cached per zoomlevel. Since we now have viewports-clusters, this no longer works. I thought about caching by zoomlevel and viewport-coordinates, by the chance of hitting the exact same combination multiple times is slim and since it already performs pretty well I decided to disable caching completely. I didn't back-out the caching-code but went for the quick approach of just disabling saving to cache so it will never find any cache.
Here is an archive with the changes files.
Bo
from android-maps-utils.
I also have implemented @ericpanorel's solution to greatly improve the performance of clustering with a large data set.
I'm using a custom onCameraChange
listener that grabs the bounds of the projection, asynchronously calculates a set of points that are within the new bounds, and then clears & adds the new points to the ClusterManager
instance. I should note that I am scaling the bounds from the projection up by a factor of 1.5, this makes the user experience a fair bit nicer (clusters visible on screen don't change so rapidly compared with not scaling the bounds).
I would be interested to know if my workaround is the right way of doing things, seems to me like this functionality should be baked into the clustering algorithm somehow?
Thanks.
from android-maps-utils.
Having used android-maps-extensions and google-maps-clustering I would recommend the following:
https://github.com/sharewire/google-maps-clustering
Performance is fantastic and the code is well written and easily extended. Also has nice animations when the map moves and the clusters regroup.
from android-maps-utils.
Ok I tried it myself. The following code clears all items from the clustermanager and then dynamically checks if markers are in the visible area of the map after a camera position change. If they are, they are added to the clustermanager. This happens in an asynctask and the performance acceptable (at least on a Nexus 7) with ~ 20.000 Markers:
// setup camera change listener to fire the asynctask
mGoogleMap.setOnCameraChangeListener(new OnCameraChangeListener() {
@OverRide
public void onCameraChange(CameraPosition position) {
new DynamicallyAddMarkerTask().execute(mGoogleMap.getProjection().getVisibleRegion().latLngBounds);
}
});
private class DynamicallyAddMarkerTask extends AsyncTask<LatLngBounds, Void, Void> {
protected Void doInBackground(LatLngBounds... bounds) {
mClusterManager.clearItems();
for (Marker currentMarker : listOfMarkers) {
if (bounds[0].contains(currentMarker.mLocation)) {
mClusterManager.addItem(currentMarker);
}
}
return null;
}
@OverRide
protected void onPostExecute(Void result) {
mClusterManager.cluster();
}
}
storkme: how did you scale the bounds to be 1.5 times larger than the visible screen area?
I tried this:
final LatLngBounds screenBounds = mGoogleMap.getProjection().getVisibleRegion().latLngBounds;
float[] screenDiameterInMeters = new float[1];
Location.distanceBetween(screenBounds.northeast.latitude, screenBounds.northeast.longitude, screenBounds.southwest.latitude, screenBounds.southwest.longitude, screenDiameterInMeters);
screenDiameterInMeters[0] *= .5f;
final LatLngBounds.Builder builder = new LatLngBounds.Builder();
builder.include(SphericalUtil.computeOffset(screenBounds.northeast, screenDiameterInMeters[0], 25d));
builder.include(SphericalUtil.computeOffset(screenBounds.southwest, screenDiameterInMeters[0], 215d));
But somehow it doesn't work when I'm above the equator?
Greetings and thanks for the great library!
from android-maps-utils.
I think it had to do something with too long distances. When i keep them about 2.000.000 meters it works!
@broady are you going to include something like this in the clustermanager/alghorithm?
Thank you for the library!
from android-maps-utils.
Hey folks.
I've actually left the Maps team, so everything I do on this project is in my spare time.
Here's what I think should happen:
DefaultClusterRenderer
implementsGoogleMap.OnCameraChangeListener
.- Somehow pipe camera change events to
DefaultClusterRenderer
. - On the first camera change event, set a flag that changes the renderer to only render markers inside the visible bounds.
- Update visible markers when the camera changes and/or clusters change.
Unfortunately the loose coupling design of the library isn't conducive to this kind of stuff. I think it was a mistake.
@jfschmakeit @markmcd in case they have spare cycles.
from android-maps-utils.
I can confirm, this https://github.com/mg6maciej/android-maps-extensions/tree/develop can handle a lot of markers without performance issues
from android-maps-utils.
Closing this. Please reopen if you believe it should be addressed. Thank you for your contribution.
from android-maps-utils.
I think I know why: the markers are distributed all over the the earth and clusters are calculated for all of them although only a small area is visible for the user. So imho a fix would be that clusters are only computed in the visible area.
from android-maps-utils.
Thanks for reporting!
This is roughly fixed in HEAD, but needs more work, and is why v0.2 (which includes clustering) hasn't been widely publicised. Stand by. :-)
from android-maps-utils.
Also, very curious what you're doing with 15K markers - feel free to mail me privately if you don't want to talk about it here.
from android-maps-utils.
Seems like adding a simple
if (onScreen)
before line 353 in DefaultClusterRenderer.java fixes the problem. But I'm unsure what other implications this has..
from android-maps-utils.
Working on this next.
from android-maps-utils.
Hey,
I have 3600 markers and when I zoom the map in a high zoom level it is very slow although I have a Nexus 5. When zooming is it possible to explode only visible clusters ( clusters on screen ) ? I think it will improve performances.
from android-maps-utils.
I have encountered a similar problem with this myself, but it was with polylines. And here's what I did.
1.) In my activity/fragment, i have a SparseArray variable, which holds all the coordinate set (not Polyline yet)
2.) I added a listener to the camera change event onCameraChange such that when this event occurs, I fire off a local AsyncTask, to solve which coordinate set(s), are touched/inside the map's bounds. The map's bounds can be solved by calling map.getProjection().getVisibleRegion().latLngBounds. In this AsyncTask, I perform a tight loop (or something smarter) on my sets of coordinates to find out which ones should be displayed
3.) On the AsyncTask's onPostExecute method, I create the Polyline(s) for those coordinate sets, that are in, or touched by the map bounds (returned by doInBackground...
I'm sure for markers, this can be implemented, or maybe implemented already in the library...
from android-maps-utils.
Thank you. Using the idea of your solution helped me improve performances.
from android-maps-utils.
could anyone of you post the code for doing this? I think it'd help many of us!
from android-maps-utils.
I'd also like to suggest that the Algorithm interface include a cancel() member, and that ClusterManager.cluster() call it to stop any previous expensive clustering operations. It makes a difference when the user makes multiple rapid camera changes, e.g. zooming or panning. Better than relying on Java to kill the ClusterTask background thread IMO.
from android-maps-utils.
Is there any practicable solution for this issue yet?
Edit:
gruelfin's solution with the AsyncTask helped me a lot. Although, it still is imperative to keep track of one big list of markers over which you have to iterate. Im my case, I have some built in filters, the user can set, in order to minimize the number of total markers. Every time such a setting is changed, I have to update the listOfMarkers. But it is a small nuisance compared to having to recluster this complete list.
from android-maps-utils.
Any word on this? I have about 8k markers and the lib is unusable at that point unfortunately. And I say unfortunately with great respect as this is a beautiful lib as far as UI, very easy to use and documented well. Really hoping I can use this soon :(
At a high level zoom things are pretty good. However as I start to zoom in performance degrades. I assume this is due to markers and clusters off the viewport being drawn/redrawn as I zoom.
Then once I am zoomed all the way in if I zoom out quickly I notice a huge hit. All the markers are unclustered (as I was all the way zoomed in before). I keep zooming out until I can see the entire map. I can see all (unclustered) markers and it takes 20-30 seconds to loop through all 8K markers and place them back into a clusters.
All of this probably relates to the fact that off screen markers and clusters are being drawn. However I also wonder if this is slow due to the animations. In cases like this it might make sense to be able to turn off animations and just remove and redraw all clusters and marker on zoom change.
from android-maps-utils.
I made the following changes locally to get the library to work reasonably
well with lots of markers:
- Disable animation completely. There is a constant you can change in the
existing code for this. - When camera motion stops, use an AsyncTask to remove all items from the
ClusterManager, and then re-add only those visible on the screen. (I do
this only if there's a large number of markers.) - In ClusterManager, I imported the
mClusterTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) change from
upstream (I had an older version of the library). - I added a cancel() method on Algorithm, and a periodic check in
NonHierarchicalDistanceBasedAlgorithm.getClusters, so that I could cancel
previous clustering operations when starting a new one.
I like this library but I wish the team were more responsive to obvious
problems like this and
https://code.google.com/p/gmaps-api-issues/issues/detail?id=5313
On Mon, Apr 6, 2015 at 6:44 PM, Billy Newman [email protected]
wrote:
Any word on this? I have about 8k markers and the lib is unusable at that
point unfortunately. And I say unfortunately with great respect as this is
a beautiful lib as far as UI, very easy to use and documented well. Really
hoping I can use this soon :(At a high level zoom things are pretty good. However as I start to zoom in
performance degrades. I assume this is due to markers and clusters off the
viewport being drawn/redrawn as I zoom.Then once I am zoomed all the way in if I zoom out quickly I notice a huge
hit. All the markers are unclustered (as I was all the way zoomed in
before). I keep zooming out until I can see the entire map. I can see all
(unclustered) markers and it takes 20-30 seconds to loop through all 8K
markers and place them back into a clusters.All of this probably relates to the fact that off screen markers and
clusters are being drawn. However I also wonder if this is slow due to the
animations. In cases like this it might make sense to be able to turn off
animations and just remove and redraw all clusters and marker on zoom
change.ā
Reply to this email directly or view it on GitHub
#29 (comment)
.
from android-maps-utils.
@akirmse agreed, wish issues like this would get more attention. Of course I am not contributing so I can't really complain ;)
Is this being actively worked by contributors, or has anyone forked, maybe a possible PR for this?
from android-maps-utils.
For more than two years the clustering element of this library is totally unusable. Clustering more than 100 items even on a big area brings huge performance issues. This should be either adressed or removed completely from the library. The suggestion by @akirmse does significantly improve the performance of this library.
from android-maps-utils.
Heres my hacked version :
Update NonHierarchicalDistanceBasedAlgorithm getClusters function to
public Set<? extends Cluster> getClusters(double zoom, LatLngBounds bounds)
Add the following if to the QuadItem candidate for each loop
if(!bounds.contains(candidate.mPosition)) {
continue;
}
Replace ClusterManager.cs onCameraChange function with the following:
mMap.clear();
mPreviousCameraPosition = mMap.getCameraPosition();
currentBounds = mMap.getProjection().getVisibleRegion().latLngBounds;
Set<? extends Cluster> results = mAlgorithm.getClusters(mPreviousCameraPosition.zoom, currentBounds);
for(Cluster cluster : results)
{
// Strip out DefaultClusterRendered.cs perform method and render each cluster accordingly here
}
from android-maps-utils.
@akirmse can you show your example in code?
from android-maps-utils.
@broady any update on this one? Your clustering looks very cool, the animations are great, but it just cannot handle more markers. And this is a big problem, since the point of clustering that we have lots of markers. I am currently using this algorithm to only cluster the visible markers, but this also has drawbacks (clusters change a lot, user have to wait a little bit when cluster show up after changing camera).
Are you planning to implement robust solution? Thanks in advance for your answer, and for this great library in general! :)
from android-maps-utils.
I had to modify this library to my needs to get it working. I turned off animation and have it refreshing async on camera change. I have about 50k+ markers and it works great. It will only show markers in the visible region.
from android-maps-utils.
Anyone on maps team supporting this or is it left to die?
from android-maps-utils.
@newmanw I've just written down the exact steps of my preferred solution. Please discuss, send a pull request, etc! :)
from android-maps-utils.
@broady it is sad you no longer work on this officially, and no one else took over. This is an important project, even referred from the official doc. Thanks for still managing the project, your time is much appreciated!
I already tried to implement your solution, but only in a very naive way. ClusterManager
automatically calls mRenderer.onCameraChange
if the renderer implements OnCameraChangeListener
, so piping that is already done. This is my onCameraChange
method in the renderer:
@Override
public void onCameraChange(CameraPosition cameraPosition) {
LatLngBounds bounds = map.getProjection().getVisibleRegion().latLngBounds;
for (Marker marker : clusterManager.getClusterMarkerCollection().getMarkers()) {
if (bounds.contains(marker.getPosition())) {
marker.setVisible(true);
} else {
marker.setVisible(false);
}
}
}
There are two problems with this approach:
- when panning/rotating, the movements are slow because this loop blocks the drawing thread for too much time by iterating over all the clusters and checking they are in the bounds or not
- this does not help at all when zooming, because the markers are added asynchronously that time
So a more complicated solution is needed, but i could not figure that out.
from android-maps-utils.
+1 for this issue
from android-maps-utils.
As one solution to slow animation you can now choose to disable the animations, which was added in 27f7a5b
from android-maps-utils.
I've a map with 4k markers on it. I've tried the VisibleBasedAlgorithm but the performance remains very very slow.
When I zoom out the map, I've to wait some seconds before the clustering ends.
When I zoom in, the cluster disappear very slow, so the user is confused.
My experience:
- I've disabled the animations;
- I've used the https://github.com/zamesilyasa/android-maps-utils (branch issue-82)
- I've added further optimization on ClusterRenderer class
Is there a way to avoid the cluster expansion when the markers aren't visible on maps bounds? I think that this could be only solution.
from android-maps-utils.
@CeccoCQ, you can try this optimised variant of Screen Based algorith zamesilyasa@8694da3.
from android-maps-utils.
Hi @Gary111,
I've finish the experiments with your code just now and I've seen some improvements.
By your experience, is there a way to increase the bitmap drawing performance?
from android-maps-utils.
Are you sure you are using as small bitmaps(thumbnails) for markers as possible? if bitmap is large, it will slow rendering down.
from android-maps-utils.
@CeccoCQ, If you mean drawing markers, I use disk and memory LRU cache for this. So I generate icon only once and save it in cache, next time I just extract it form cache. It increases performance a little.
from android-maps-utils.
Hi, thanks for your responses.
This is my code used to create the bitmap (into MyRenderer constructor):
public LruCache<Integer, BitmapDescriptor> mIcons;
.....
int sizeSelected = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, context.getResources().getDisplayMetrics()));
Bitmap selectedBitmap = BitmapFactory.decodeResource(context.getResources(), mso.markerSelected).copy(Bitmap.Config.ARGB_8888, true);
Bitmap selected = Bitmap.createScaledBitmap(selectedBitmap, sizeSelected, sizeSelected, true);
BitmapDescriptor descSelected = mIcons.get(mso.markerSelected);
if (descSelected == null) {
descSelected = BitmapDescriptorFactory.fromBitmap(selected);
mIcons.put(markerTypeId, descSelected);
}
then into the onBeforeClusterItemRendered method:
BitmapDescriptor desc = mIcons.get(markerTypeId);
The original bitmap is a png file of 294x294px of 15Kb
from android-maps-utils.
So... over 4 years down the line is there any progress on this issue??
I've just migrated from AME to AMU and the performance is MUCH MUCH worse.
I thought it would be a step forwards in performance but it's been a huge leap backwards.
Even with animations turned off it takes an age for clusters to be regrouped when zooming out.
from android-maps-utils.
I am facing the same issue. Dragging too slow when zooming out. Is there any way to fasten this?
from android-maps-utils.
The fact this issue has been open for 5 years and the lack of response from anyone suggests this will not be addressed. Iād recommend a 3rd party clustering library if you want something that works.
from android-maps-utils.
This issue has been automatically marked as stale because it has not had recent activity. Please comment here if it is still valid so that we can reprioritize. Thank you!
from android-maps-utils.
Related Issues (20)
- Add support for Advanced Markers HOT 1
- Ability to specify `anchor` marker option for `ClusterItem`
- Refdocs stuck at v3.4.0 HOT 3
- Change documentation link to GitHub page HOT 1
- Set documentation title as "android-maps-utils" HOT 1
- A lot of unnecessary warnings HOT 1
- Add StreetViewSource parameter to fetchStreetViewData HOT 2
- Remove outdated CHANGELOG.md file HOT 2
- Add kml layer in compose HOT 1
- Creating Kotlin demos for android-maps-utils
- Add Header Check to android-maps-utils
- Improve javadoc descriptions to marker clustering algorithms
- java.lang.IllegalArgumentException : Unmanaged descriptor HOT 3
- Android Map development encountered Google Map deadlock issue: HOT 3
- Incorrect operation of the marker information window HOT 3
- Multiple google api keys (MAPS_API_KEY) per Application HOT 1
- Odd cluster behavior during clustering HOT 1
- Add AdvancedMarkerManager Collection HOT 2
- setOnMarkerDragListener does not report marker drag event HOT 2
- Google Maps is not loading on Android 8 HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
š Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. ššš
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ā¤ļø Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from android-maps-utils.