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
Related
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.
I am trying to write an android application that use google map to show some points (shop places) on the map. I have shop specifications including LatLng, title, etc on a database server and have a rest api to access them. my problem is the total amount of shops are too much, so I can't get all of them and show them on the map, so I need to have some kind of lazy loading showing to access just shops that are required.
I found this link, but it isn't lazy loading, what I understood is that it gets all the item first and show them with marker, which isn't what exactly I need.
Is it possible to do this? if yes how ? please give me some example or tutorial if it is possible.
thanks in advance.
Why you dont use Google Maps Android Marker Clustering Utility?
I used to load 12000 markers in a map and is ok for me. I load all of this markers at the same time.
To use lazy loading you can determine the viewable map area and load only this markers.
You can use OnCameraChangeListener to determine it:
map.setOnCameraChangeListener(new OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition position) {
BOUNDS = map.getProjection().getVisibleRegion().latLngBounds;
Float mapZoom = map.getCameraPosition().zoom;
LatLng latlong = map.getCameraPosition().target;
updateItems(BOUNDS);
mapZoom = map.getCameraPosition().zoom;
}
});
We all know that some of the predefined landmarks on Google Maps does not appear on a lower zoom level, but on a higher zoom level, it suddenly appears. I would like to know If I can make a customized marker not appear at lower zoom levels, then appear at higher ones.
EDIT: Here is a snippet of my code.
// Changing marker icon
marker.icon(BitmapDescriptorFactory.fromResource(R.drawable.gasbig));
// adding marker
map.addMarker(marker);
//position on Center
CameraPosition cameraPosition = new CameraPosition.Builder().target(
new LatLng(14.635356, 121.03272914)).zoom(16).build();
map.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
map.setOnCameraChangeListener(new OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition arg0) {
if(arg0.zoom > 7){
marker.visible(true);
}
}
});
I tried the suggestion of MaciejGórski but the marker still appears on all zoom levels. I'm sorry about the question I'm still an android newbie.
Thanks in advance.
You can do that for any Marker you want: call setVisible in OnCameraChangeListener.onCameraChange callback with true or false depending on CameraPosition.zoom value.
Edit after question edit:
You need to keep a reference to Marker instead of MarkerOptions:
// adding marker
marker = map.addMarker(markerOptions);
and call setVisible on that marker:
#Override
public void onCameraChange(CameraPosition cameraPosition) {
marker.setVisible(cameraPosition.zoom > 7);
}
Note: setVisible is always called there, but this might not be optimal when using many Markers.
You could possibly do it by modifying my answer to: Android Maps v2 - animate camera to include most markers
Otherwise using Android Maps Extensions might be a good choice. No experince with your specific needs though.
Just realized I might have misunderstood the question. Thought you meant your own markers. Nevertheless have a look at the extensions library. Could very well be that they have something useful.
I'm drawing a lot of markers on the map and when they located close they overlap each other. So I want to hide some markers on small zoom and show more markers when user zooming map. Like more zoom in, more markes. Here is example code of activity and creating of markers, as you can see I'm using google maps android api v2:
public class MainActivity extends FragmentActivity
{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GoogleMap map = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap();
map.getUiSettings().setMyLocationButtonEnabled(true);
createMarkers(map);
}
private void createMarkers(GoogleMap map) {
double initLat = 48.462740;
double initLng = 35.039572;
for(float i = 0; i < 2; i+=0.2) {
LatLng pos = new LatLng(initLat + i,initLng);
map.addMarker(new MarkerOptions()
.position(pos)
.icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_launcher)));
}
for(float i = 0; i < 2; i+=0.2) {
LatLng pos = new LatLng(initLat, initLng + i);
map.addMarker(new MarkerOptions()
.position(pos)
.icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_launcher)));
}
}
It sounds like typical task for me, but i still didn't manage to find working solution. I've read this article https://developers.google.com/maps/articles/toomanymarkers but I've no idea how to implement it on android. Does anybody has some working code which can do this?
Here is my solution to this problem: https://github.com/Bersh/MarkersCluster
Maybe it's not the best solution, but it works for me. Hope it'll be usefull.
You are basically asking how to implement clustering on Android. As far as I know, there is no solution provided by Google to do this. The document you referenced in the comments (developers.google.com/maps/articles/toomanymarkers) is referring to the google maps javascript API. Unfortunately, none of the clustering related code is yet available on Android.
You will have to come up with your own algorithm to cluster Markers. The developers.google.com/maps/articles/toomanymarkers document that you pointed to may be a good starting point to decide what algorithm will work best for you (grid based clustering, distance based clustering, etc).
Another option that's slightly easier to implement (but not perfect) might be to change the Marker image to a smaller icon when the zoom level has changed so they don't overlap as much. Check out my answer to this question: https://stackoverflow.com/a/13976080/1103584
You could use that answer to determine when you've crossed a "zoom threshold" and change the Markers to something smaller once you've zoomed out passed a certain threshold and to a bigger image when you've zoomed back in. I use this trick in a couple of my own apps to change regular icons to small dot images when zoomed out, and back to the regular icon when you zoom back in.
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);