Android - google maps gets stuck - android

I'm developing a App which display a Google map and a bunch of markers on it. There's a lot of markers so I divided them in smaller groups and display only those, which are in some bounds depending on the current position of the camera.
To do that I'm using the GoogleMap.OnCameraIdleListener. First I remove the listener, do my calculations and drawing and then I restore the listener to the Fragment containing my map:
#Override
public void onCameraIdle() {
mMap.setOnCameraIdleListener(null);
clearMap();
findTheMarkersInBounds();
displayTheMarkers();
mMap.setOnCameraIdleListener(this);
}
This way I only draw the markers I need to display and the performance is way better then having 1000 markers on the map at once. I also draw about the same number of polylines but that's not the point now.
For some strange reasons, after some panning and zooming the maps doesn't respond anymore. Can't zoom it nor pan it. App displays a dialog that it is not responding and I should wait or close the app. No erros are displayed in logcat. I can't exactly tell when this happens. Sometimes after the first pan, sometimes I can move around 2-3 minutes. Same thing happens on the emulator and on the physical device.
Anyone experienced something like this? Thanks!
Or am I approaching this the wrong way? How else should I optimize the map to display about 1000 markers and polylines. (The markers have text on them, so it can't be the same Bitmap and all of the polylines can have different colors and need to be clickable, so I can't combine them into one large polyline)
EDIT. A little more info about my methods:
After all the marker positions are loaded from the internal database, I do a for-loop through all of them and based on their position and I place them to the corresponding region. Its an 2D array of lists.
My whole area is divided to 32x32 smaller rectangular areas. When I'm searching for the markers to display, I determine which region is in view and display only those markers, which are in this area.
This way I don't need to loop over all of the markers.
My methods (very simplified) look like this:
ArrayList<MarkerObject> markersToDisplay = new ArrayList<MarkerObject>();
private void findTheMarkersInBounds() {
markersToDisplay.clear();
LatLngBounds bounds = mMap.getProjection().getVisibleRegion().latLngBounds;
int[] regionCoordinates = getRegionCoordinates(bounds); // i, j coordinates of my regions [0..31][0..31]
markersToDisplay.addAll(subdividedMarkers[regionCoordinates[0]][regionCoordinates[1]]);
}
private void drawMarkers() {
if ((markersToDisplay != null) && (markersToDisplay.size() > 0)) {
for (int i=0; i<markersToDisplay.size(); i++) {
MarkerObject mo = markersToDisplay.get(i);
LatLng position = new LatLng(mo.gpsLat, mo.gpsLon);
BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(createMarker(getContext(), mo.title));
GroundOverlay m = mMap.addGroundOverlay(groundOverlayOptions.image(bitmapDescriptor).position(position, 75));
m.setClickable(true);
}
}
}

It is hard to help you without source code of findTheMarkersInBounds() and displayTheMarkers(), but seems, you need different approach to increase performance, for example:
improve your findTheMarkersInBounds() logic if it possible;
runfindTheMarkersInBounds() in separate thread and show not all markers in same time, but one by one (or bunch of 10..20 at one time) during findTheMarkersInBounds() searching;
improve your displayTheMarkers() if it possible, actually may be use custom drawing on canvas (like in this answer) instead of creating thousands Marker objects.
For question updates:
Small improvements (first, because they are used for main):
pass approximately max size of markersToDisplay as constructor parameter:
ArrayList<MarkerObject> markersToDisplay = new ArrayList<MarkerObject>(1000);
Instead for (int i=0; i<markersToDisplay.size(); i++) {
use for (MarkerObject mo: markersToDisplay) {
Do not create LatLng position every time, create it once and store in MarkerObject fields.
Main improvement:
This lines are the source of issues:
BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(createMarker(getContext(), mo.title));
GroundOverlay m = mMap.addGroundOverlay(groundOverlayOptions.image(bitmapDescriptor).position(position, 75));
IMHO using Ground Overlays for thousands of markers showing is bad idea. Ground Overlay is for several "user" maps showing over default Google Map (like local plan of Park or Zoo details). Use custom drawing on canvas like on link above. But if you decide to use Ground Overlays - do not recreate them every time: create it once, store references to them in MarkerObject and reuse:
// once when marker created (just example)
mo.overlayOptions = new GroundOverlayOptions()
.image(BitmapDescriptorFactory.fromBitmap(createMarker(getContext(), mo.title)))
.position(mo.position, 75))
.setClickable(true);
...
// in your drawMarkers() - just add:
...
for (MarkerObject mo: markersToDisplay) {
if (mo.overlayOptions == null) {
mo.overlayOptions = createOverlayOptionsForThisMarker();
}
mMap.addGroundOverlay(mo.overlayOptions)
}
But IMHO - get rid of thousands of Ground Overlays at all - use custom drawing on canvas.

After further investigation and communication with the google maps android tech support we came to a solution. There's a bug in the GroundOverlay.setZIndex() method.
All you have to do is to update to the newest API version. The bug is not present anymore in Google Maps SDK v3.1.
At this moment it is in Beta, but the migration is pretty straightforward.

Related

How to deal with Large number of ClusterItems in Cluster - Android GoogleMaps V2

I am using GoogleMapsV2. I have around 10 000 cluster items which I bulk add to ClusterManager (ClusterManager.addItems(Collection).
My problem is that the clustering and de-clusterin process is lagging and slow. I believe it shouldn't be that slow with around 10 000 items. Testing on OnePlus3 (with 6GB or ram) so it's not the phone.
Here is cluster relate code in activity:
// Rent markers
rentMarkerList = new ArrayList<>();
rentClusterManager = new ClusterManager<OffersMarker>(this, gmap);
rentClusterRenderer = new OffersClusterRenderer(this, gmap, rentClusterManager);
rentClusterManager.setRenderer(rentClusterRenderer);
for (AdMarker adMarker : adsArray) { //<-- adsArray.size() = 6500
OffersMarker offsetItem = makeOffersMarker(adLocation, nrOfAds, realEstateAdIds, OffersMarker.DEALTYPE.SALE_CODE);
rentMarkerList.add(offsetItem);
}
// Do bulk add of Markers into ClusterManager.
rentClusterManager.addItems(saleMarkerList);
rentClusterManager.cluster();
I went over my ClusterRenderer and I am not performing any large operations in onBeforeClusterItemRendered() and onBeforeClusterRendered().
It seems that the application is much slower when doing Clustering than it is in de-clustering but that may also be because clustering is done usually on a much smaller zoom level and thus more markers are displayed.
My android monitor shows that the clustering process alters the memory and CPU and not the GPU so it's not a graphics problem.
Solutions I tried:
Doing dynamic cluster rendering like described here. In this case you loop through your markers list and see which are in the Viewport bounds so you only display those markers. This made the whole thing worse because I had to loop through all the 10 000 markers every time onCameraIdle... was called.
Any ideas where to start optimizing this problem?
Do you use rentMarkerList anywhere else? If not, you can refactor your code in this way:
rentClusterManager = new ClusterManager<OffersMarker>(this, gmap);
rentClusterRenderer = new OffersClusterRenderer(this, gmap, rentClusterManager);
rentClusterManager.setRenderer(rentClusterRenderer);
for (AdMarker adMarker : adsArray) { //<-- adsArray.size() = 6500
rentClusterManager.addItem(makeOffersMarker(adLocation, nrOfAds, realEstateAdIds, OffersMarker.DEALTYPE.SALE_CODE));
}
rentClusterManager.cluster();
So you don't create an extra list of markers and don't add them all at the same time, but add them one by one.
You can also repleace foreach loop with simple for loop. According to this Android performance patterns episode, it works faster.
I don't think that that will significantly improve the performance, it's just little optimization tips, that may help.
After straggling with 30K markers on ios i just dropped google cluster manager, it has very poor preformance and not scalable at all. there plenty other free cluster libs, just drop google-utiils.
I have swapped it for ClusterKit - works grate!
https://github.com/hulab/ClusterKit - (iOS)
There is implementation of a view-based clustering algorithm in android-maps-utils library that can be used for better performance of clustering.
I had 5000+ markers and clustering was struggling when zooming in and out. After setting Algorithm to NonHierarchicalViewBasedAlgorithm, clustering is very smooth and faster.
mClusterManager = new ClusterManager<>(this.mContext, mGoogleMap);
DisplayMetrics metrics = new DisplayMetrics();
this.mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
mClusterManager.setAlgorithm(new NonHierarchicalViewBasedAlgorithm<MyItem>(metrics.widthPixels, metrics.heightPixels));

How to move/animate a marker smoothly along a poly-line points on google maps?

I am a newbie in android development and currently working on an app where i need to show a marker moving along a path(poly-line). I have googled a lot for the functionality and got various answers too but for now i have selected the answer from this
question which better suits the requirements. But the problem with the solution is the marker moving very fast. Please suggest me any correction or other solution that will slow down the speed of animation of moving marker or something like that.
Thanks
From this answer in related SO post, you need to update the marker more than every 1/10 fraction of the polyline (at least every few pixels). Call the update method more frequently and don't delete and re-add the marker.
Sample code:
var counter = 0;
interval = window.setInterval(function() {
counter++;
// just pretend you were doing a real calculation of
// new position along the complex path
var pos = new google.maps.LatLng(35, -110 + counter / 100);
marker.setPosition(pos);
if (counter >= 1000) {
window.clearInterval(interval);
}
}, 10);
Check these related SO threads which might help:
move marker on google maps api 2
Google map: moving marker and map together smoothly along with user?
Hope this helps!

How to find Center Point from geometry arcgis android?

I am developing one application in that i want to show call out in center of geometry on my map I am new to arcgis.I tried so much but i am unable to get call out in center,please anybody help me how to solve this problem
my code
SimpleFillSymbol sfs = new SimpleFillSymbol(
Color.RED);
sfs.setAlpha(5);
graphic = new Graphic(feature.getGeometry(),
sfs, feature.getAttributes());
Polygon polygon = (Polygon) graphic
.getGeometry();
int polygonpointscount=polygon.getPointCount();
if(polygonpointscount!=0)
{
pointsize=polygonpointscount/2;
}
Point midpoint = polygon.getPoint(pointsize);
Callout callout = mMapView.getCallout();
if (callout != null
&& callout.isShowing()) {
callout.hide();
}
//Set the content, show the view
callout.setContent(adminsearchloadView(governorate_Name,
area_Name,
block_Number));
callout.setStyle(R.xml.calloutstyle);
callout.setMaxHeight(100000);
callout.setMaxWidth(100000);
callout.refresh();
callout.show(midpoint);
Short answer: use GeometryEngine.getLabelPointForPolygon(Polygon, SpatialReference).
Long answer:
From your code...
int polygonpointscount=polygon.getPointCount();
if(polygonpointscount!=0)
{
pointsize=polygonpointscount/2;
}
Polygon.getPointCount() returns the vertices of the polygon. For example, if the polygon is a rectangle, getPointCount() returns the corners. So your Callout will be at one of the corners instead of at the centroid.
Instead, use GeometryEngine.getLabelPointForPolygon(Polygon, SpatialReference) . It doesn't guarantee to return the centroid, but it returns an interior point that is good for labeling (and it looks like the centroid to me). Make sure you pass a SpatialReference object that tells getLabelPointForPolygon what the spatial reference of your polygon is.
If you must have the centroid, you'll need to create a geoprocessing service based on ArcGIS's Feature to Point tool. However, getLabelPointForPolygon is much easier to implement and faster to execute and probably satisfies your need.
If you use arcgis server you can try "feature to point": http://resources.arcgis.com/en/help/main/10.1/index.html#//00170000003m000000 to calculate centroid layer.

Clickable Polylines Google Maps API Android

I would like to click a spot on a Google maps v2 android map. If the clicked point intersects a point on a polyline path, then display the polyline. I do not see any documented clickable events for polylines in android. I tried to extend the current Polyline object (marked final)
What other options do I have?
You can use library:
https://github.com/googlemaps/android-maps-utils
And detect clicks to polyline using next method (in OnMapClickListener):
PolyUtil.isLocationOnPath(point, polyline.getPoints(), isGeodesic, tolerance);
With the recent update of the maps api, v8.4, introduces clickable Polyline
As mentioned in the doc:
Use the OnPolylineClickListener to listen to click events on a
clickable polyline. To set this listener on the map, call
googleMap.setOnPolylineClickListener(...). When
a user clicks on a polyline, you will receive an
onPolylineClick(Polyline) callback.
gradle-dependency:
compile 'com.google.android.gms:play-services-maps:8.4.0'
implement callback: GoogleMap.OnPolylineClickListener
initialize Polyline:
Polyline polyline = googleMap.addPolyline(options);
polyline.setClickable(true);
...
receive events
#Override
public void onPolylineClick(Polyline polyline) {
....
}
Happy coding :)
Register an OnMapClickListener. Determine if a given click is on your line yourself. If it is, do whatever it was you wanted to do in this case.
I had a similar issue where I could not process click events on polylines. I was using Xamarin for Android which is C# but the functionality is largely the same as the Android Java Libraries in this case.
In the end, I ended up doing what seemed to be the only option.
This involved processing all of the midpoints of my polylines(of which there were around 1300). On every OnMapClick, I took the LatLng of the click event and performed a distance formula between it and the midpoint of all polylines in the static List<PolylineOptions>. I then attached a map marker to the closest polyline.
From a tap on a polyline, it pops up a marker in about a quarter of a second.
I imagine the implemented marker click events from the Google Maps API work in a similar way.
Here is the for loop that handles finding the closest point to a click.
int i = 0;//create an indexer for the loop
double shortestDist = 100;//set an initial very large dist just to be safe
int myIndex = 0;//set variable that will store the running index of the closest point
foreach (PolylineOptions po in myPolylines) {
var thisDist = Distance (point, midPoint (po.Points [0].Latitude, po.Points [0].Longitude, po.Points [1].Latitude, po.Points [1].Longitude));//calculate distance between point and midpoint of polyline
if (thisDist < shortestDist) {
shortestDist = thisDist;//remember current shortest distance
myIndex = i;//set closest polyline index to current loop iteration
}
i++;
}
I know it isn't the prettiest code but it gets the job done. I didn't see a real answer to this anywhere on the internet so here it is. It could probably be made more efficient by calculating the midpoints beforehand and storing them in an equally sized list and then not having to call the midpoint formula for each polyline on every map click but it works really fast already.
EDIT
I do my testing on a galaxy s3 by the way, so I think it's not too inefficient.
If you are using com.google.android.gms:play-services-maps:8.4.0 then it includes polylines click listener
googleMap.setOnPolylineClickListener(new GoogleMap.OnPolylineClickListener()
{
     #Override
     public void onPolylineClick(Polyline polyline)
      {
          //do your work selected polyline
     }
});
PolylineOptions line = new PolylineOptions();
Polyline polyline = googleMap.addPolyline(line);
polyline.setClickable(true);

Android Maps API v2 - approaches to marker rotation?

We're porting an app from the v1 Maps API to the v2 API, and having trouble with markers.
We need markers that point in a specific geographic direction. In V1, we could build the bitmap pointing in the right direction at draw time, but in V2 the marker bitmap can't be changed.
I'm not sure if the best approach is to destroy and re-build all our markers when the map is rotated (which sounds like a performance problem), or to try drawing them all ourselves. That could be via a TileOverlay or via a view of our own that we sat on top of the map.
I don't really like any of these approaches. Has anyone tried any of them ?
UPDATE:
I've tried drawing via a view of our own, but that was far too laggy when the map was dragged.
I'm now destroying & recreating the markers, but that is (as expected) a performance problem, taking ~2000mS to update 60 markers.
Good news everyone! Google has added rotation to the Maps API, so we don't have to roll our own implementations anymore.
They have also added flat markers, which I guess is more related to the original question. A flattened marker will always stay in the orientation it was originally drawn on the map: https://developers.google.com/maps/documentation/android/marker#flatten_a_marker
The only requirement is that you reference the latest version of Google Play Services.
I'm also rewriting my app (Runbot) for the new API and had to figure out how to create custom markers representing milestones (like 1km, 2km, ...) and how to show or show not all of them depending on the zoom level.
I had a custom drawable that I used for the v1 API and what I do now to render the markers is about this (Position is a class of my own that holds the position and further information; all needed here is its LatLng property):
private void addMarker(Position p, MilestoneDrawable milestone) {
if (mMarkers.containsKey(p)) {
mMarkers.get(p).setVisible(true);
} else {
Marker m = mMap.addMarker(new MarkerOptions()
.position(p.latLng)
.icon(BitmapDescriptorFactory.fromBitmap(Util.drawableToBitmap(milestone)))
.anchor(0.5f, 1.0f) // bottom center
);
mMarkers.put(p, m);
}
}
Besides creating and adding the custom markers, what you see is that I keep the markers in a HashMap so I do not have to destroy and create them all the time. When it comes to zooming and deciding which ones to show, I first set all of the markers to invisible and than call addMarker() for those I want to be shown, and those which I already have in the HashMap I simply make visible again.
I hope this helps you a bit. I have a bit of mixed feelings towards the new API...
I had a similar problem where I had markers that needed to rotate. My solution was to have the object the marker represented be responsible for generating the marker. I have a few methods in the object that look like:
protected Marker getMarker(GoogleMap map) {
if (this.marker == null) {
marker = map.addMarker(new MarkerOptions().position(location).
icon(BitmapDescriptorFactory.fromBitmap(BusMarkerImageFactory.
getMarkerIcon(heading))));
}
return marker;
}
protected void updateMarker(GoogleMap map) {
if (marker != null) {
rotateIcon();
marker.setPosition(location);
} else {
getMarker(map);
}
private void rotateIcon() {
marker.setIcon(BitmapDescriptorFactory.
fromBitmap(BusMarkerImageFactory.getMarkerIcon(heading)));
}
This is from a system that draws buses with the markers pointing in the direction they are heading, so of course, your code will be different, but the concept is very similar. Instead of rebuilding the entire marker you're keeping a reference to it somewhere and then simply resetting the icon.
Of course, drawing all those bitmaps for minor changes is a drain on memory. I used a flyweight pattern in the (incorrectly named) BusMarkerImageFactory to keep 16 images for 16 possible heading ranges. It is a static class that simply takes in the heading and returns the image that I've mapped to that range.
can't you use addMarker(new MarkerOptions()) method ?
If you need a custom marker you can create an implementation of InfoWindowAdapter and use that implementation like mMap.setInfoWindowAdapter(new CustomInfoWindowAdapter());
here is the documentation for InfoWindowAdapter

Categories

Resources