Replicating Maps Follow Current Location Feature in Android - android

I am looking to figure out how I can replicate the follow current location feature that exists in Maps on Android (and iPhone) using the Google Maps external library. The way the feature works in Maps is that when you press the button it begins panning with your location until you touch (and/or move) the map. Using the following I have been able to get my app to keep the current location on the screen but not keep the current location in the center of the screen (it pans when the location gets near the edge of the screen whereas Maps keeps it in the center the whole time):
butMapCurrentLocation.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
runOnUiThread(new Runnable() {
public void run() {
mapController.animateTo(myLocationOverlay.getMyLocation());
}
});
}
});
I also tried setCenter rather than animateTo and they seem to have the same effect.
Additionally it is worth noting I need to be able to stop this function that keeps the location in the center (such as when another button is pressed or the map is touched). The Runnable above doesn't stop right away and I cannot get it to stop properly when requested even when using .cancel as discussed here: Android: How do I stop Runnable?

There is no official way yet to do that in Google Maps v2.
You can do what you want however. There is two things you have to do:
Register for location updates from some LocationProviders (eg gps) to get the user's location. When you get the location animate map to the new location.
Stop this when the user moved the map.
So doing 1. is easy, doing 2. is not possible officially with the Maps v2 api. Look at this issue for more details.
The workaround suggested there is to create your own MapView (extend the original MapView) and override it's dispatchTouchEvent method. There you can catch any touch events and can detect if the user touched/moved the map.

Related

Android - google maps gets stuck

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.

Android Mapbox how to capture marker clicks when using CirlceClustering layer

My Current Android application employs the excellent Mapbox SDK
implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:8.0.0'
implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-annotation-v7:0.6.0'
implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-localization-v7:0.9.0'
My application displays approx 50,000 markers and I am using CircleLayer clustering.
The application works as required/expected apart from the fact I cannot see how to detect when my user clicks on any of the low level markers.
All the "Marker" related mapboxMap methods are all deprecated
and direct the developer to employ
use <a href="https://github.com/mapbox/mapbox-plugins-android/tree/master/plugin-annotation">
* Mapbox Annotation Plugin
However I cannot see how to use plugin-annotation to detect clicks on my low level markers.
What am I missing?
To detect any click on your CircleLayer you need first to implement the onMapClick or onMapLongClick methods. Then on every detected click, you need to query your source layer and see if there any features near that location.
If so, then you can get the N nearest features and handle their behaviour.
It should look like this:
#Override
public boolean onMapClick(#NonNull LatLng point) {
// Get the clicked point coordinates
PointF screenPoint = mapboxMap.getProjection().toScreenLocation(point);
// Query the source layer in that location
List<Feature> features = mapboxMap.queryRenderedFeatures(screenPoint, "MY_SOURCE_LAYER_ID");
if (!features.isEmpty()) {
// get the first feature in the list
Feature feature = features.get(0);
// do stuff...
}
return true;
}
This is a very basic way of handling clicks on your layers data. You can find this example I have slightly modified here.

Google Maps Android, detect user interation in a location aware map

I've an Android Application that displays a map.
The map is by default centered on the user position following its movements (the center is updated according to the position updates).
However I want the user to be able to use gestures to navigate throw the map. When the user starts the navigation I want the "following" to stop, and a button is displayed so that it can start again.
How can I know when the user has moved the map center?
On the GoogleMap.OnCameraChangeListener, I don't know if the change is due to a location changed or a user interaction.
I've a a kind of working solution using the OnCameraChangeListener, but its a bit "dirty" and I don't find it very nice:
map.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition position) {
// gpsPosition contains the last position obtained
if(isFollowing && gpsPosition!=null && (Math.abs(position.target.latitude - gpsPosition.latitude)>0.000001 || Math.abs(position.target.longitude - gpsPosition.longitude)>0.000001) ){
isFollowing = false;
map.getUiSettings().setMyLocationButtonEnabled(true);
}
}
});
Is there a nice Solution to the problem?
If you want to detect user interacting with the map OnCameraChangeListener is surely too late to do that. It may be called seconds after user started interacting with the map.
What you need to do, is add 2 other listeners:
OnMyLocationButtonClickListener - to know when to start tracking (set your isFollowing to true)
OnMyLocationChangeListener - when your isFollowing is true, call GoogleMap.animateCamera to the new position
So the only thing left is when you set isFollowing to false:
Create an invisible overlay in your layout (like here, but with normal View instead of custom) and assign this overlay (the View) an OnTouchListener (ok, it is 3rd, I lied that you need only 2).
In this listener always return false, but also set isFollowing to false. This when when user starts interacting with the map and you should stop automated camera movements.
I also see you are showing and hiding my location button, so do this where you change the value of isFollowing. Btw.: good idea to do that. I'll try it too.
I have never tried this, but here are some ideas off the top of my (balding) head:
Option #1: Handle the location change updates yourself, by recentering the map yourself when the location change comes in. If a camera-change event occurs that does not appear to be tied to the location change (e.g., 5+ milliseconds later), that was presumably a user modifying the map via gestures.
Option #2: Subclass the Maps V2 MapView and override touch-related methods like onTouchEvent(). In addition to chaining to the superclass, you would know that a camera change that happens very soon from now probably is from the user modifying the map via gestures.
Option #3: Do both of the above. That way, all changes to the map should touch your code, which should increase the reliability of your determining the source of the camera change.
BTW, I filed a feature request for a better solution.
The simpliest way should be saving the center Longitude and Latitude of your Map.
Now create an EventListener and check it in an if-statement:
if(oldLongLat != newLongLat){
//Something has moved
}else {
//Nothing has moved
}

Do some actions only when current location is acquired

I am designing the map-based app and I want to do certain set of steps when user's location has been acquired (blue dot has been shown on the map). On some of the devices it may take up to 1 min due to location services ramp-up process.. So the question is how do I know when Android Map managed to acquire my location, so I can start doing the rest of initialisation process?
I was thinking about implementing my own LocationListener and trigger callback when I receive location, but this solution seems worthless since I need to only know the point when current location has been acquired and blue dot has been shown. I don't care about location updates.
If you are talking about the blue dot on a map, I suppose you add the MyLocationOverlay to your map. If it is so, there is an easy way to do what you want - MyLocationOverlay has runOnFirstFix(Rannable) interface, so provided Runnable will be run as soon as the map acquires your current location:
this.currentLocationOverlay.runOnFirstFix(new Runnable() {
#Override
public void run() {
//do your magic here
}
});

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