I am simulating a car moving on a pre-recorded path on Android maps v2. When I zoom on the path by hand, it works great, but when I move the camera over the path with mMap.animateCamera(), it doesn't load the visible map area, I only get a very pixelated, low quality map. If I touch the screen and move the map or zoom a little, then it loads again this part.
How can I achieve, that it always loads clearly the visible part?
EDIT:
I added an example image: this is what I see when I don't touch the map. After I touch it, it becomes clear (similar to the bottom left part).
EDIT2:
I have an idea, that this is because Google want's to prevent the map to be cached by quickly moving the camera over an area. Is it possible, that this is the cause of this issue? (The map is showing Budapest, Hungary, which part of the map you can not download for offline use...) But here I only want to show the animation and place markers, I only need the visible area to be cached - are there any way to workaround this behaviour?
EDIT3:
The animation code:
new Thread(new Runnable() {
#Override
public void run() {
// ... Calculating and sending new location, in an infinite loop with sleeps for timing
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
mMap.animateCamera(CameraUpdateFactory.newLatLng(new LatLng(location.getLatitude(), location.getLongitude())));
}
});
}
}).start();
Finally found a solution. I was able to recreate your problem using the code you provided. I replaced your code with the following and it worked for me.
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
// ... Calculating and sending new location, in an infinite loop with sleeps for timing
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
mMap.animateCamera(CameraUpdateFactory.newLatLng(new LatLng(location.getLatitude(), location.getLongitude())));
}
});
}
}, 0, 2000);
Just remove your code for sleeping and replace the last argument in scheduleAtFixedRate (the 2000) with whatever value you were using for sleeping.
I had the same issue. Since it did not happen when not animating the camera, it had to be something related to that.
Apperently the camera has to be able to finish its operation, before it will update the background/roads etc.
Since my app updates the position every second and the map needs about 2 seconds to complete I end up with pixelated roads and no surroundings at all.
The solution is to use one of overloads of animateCamera:
public final void animateCamera (CameraUpdate update, int durationMs,
GoogleMap.CancelableCallback callback)
Moves the map according to the update with an animation over a
specified duration, and calls an optional callback on completion. See
CameraUpdateFactory for a set of updates.
I used for my case a duration of 900msec, so the animation is done before it receives a new location.
To get the callback to work you need to implement GoogleMap.CancelableCallback to your class. This requires you to add two overrides:
#Override
public void onFinish() {
}
#Override
public void onCancel() {
}
They are not required to get the problem solved, altough you are free to add extra logic there.
The call to update the camera can look like this:
cameraPosition = new CameraPosition.Builder()
.target(current)
.zoom(zoomLevel)
.bearing(bearing)
.tilt(tilt)
.build();
CameraUpdate update = CameraUpdateFactory.newCameraPosition(cameraPosition);
map.animateCamera(update, 900, this);
Related
I am using the Google Map API v2 and displaying markers on a map in a MapFragment.
I need to add many markers to the map quickly, but without locking the UI thread. There are 2 approaches I've tried, but either it takes a long time to add lots of markers or the UI thread freezes while drawing all the markers. Is there a different way to do this that works better?
Approach #1 (Adds them quickly, but locks UI thread until they are all added):
public void addMarkersFast(List<MarkerThing> thingsToShow) {
if (getMap() == null) return; // Will be null for any device without google play services installed
for (MarkerThing thing : thingsToShow) {
getMap().addMarker(createMarker(thing));
}
}
Approach #2 (Starts displaying immediately, but can take 20-30 seconds to add all the markers):
public void addMarkersSlow(final MarkerThingArray thingsToShow) {
if (getMap() == null) return; // Will be null for any device without google play services installed
MarkerThing thing = thingsToShow.get(i);
getMap().addMarker(createMarker(thing));
(new Handler()).postDelayed(new Runnable() { // give some time for main thread to draw
#Override
public void run() {
addMarkersSlow(thingsToShow, i + 1);
}
}, 1);
}
How can I do this differently to show all the markers in a 2k+ marker scenario without locking the UI thread for 3-4 seconds like approach #1 and without taking 30 seconds to gradually add them like approach #2?
2k+ markers on a single visible map region is a lot of subviews and objects to manage and draw, not to mention some potential usability issues.
Check out marker clustering:
https://developers.google.com/maps/documentation/android/utility/marker-clustering
this allows you to "cluster" nearby markers into a collapsed marker. You should attempt to get your overall number of markers/subviews down into the dozens. Anyhow, a technique like this should help.
I think you can write an AsynTask and publish the markers in the onProgress. I'm not a big fan of using AsyncTasks though, so I would do it using RxJava (with RxAndroid for Android specific schedulers).
Example:
Observable.just(thingsToShow)
.subscribeOn(Schedulers.newThread())
.map(new Func1<MarkerThing, Marker>() {
#Override
public Marker call(MarkerThing thing) {
return createMarker(thing);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Marker>() {
#Override
public void onNext(Marker marker) {
getMap().addMarker(marker);
}
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) { }
});
I am using the skobbler sdk for maps.
I use this code to center map view on current position, but even though the blue dot is at my correct current position in Los Angeles (I verify by manually going there), the map centers me at gps(0,0).
public void onCurrentPositionUpdate(SKPosition currentPosition) {
this.currentPosition = currentPosition;
mapView.reportNewGPSPosition(this.currentPosition);
if(firstPositionUpdate){
firstPositionUpdate = false;
mapView.centerMapOnCurrentPosition();
}
}
and heres my code for initializing the map:
private void initializeMapView() {
currentPositionProvider = new SKCurrentPositionProvider(getActivity());
currentPositionProvider.setCurrentPositionListener(this);
if (DemoUtils.hasGpsModule(getActivity()) && ((LocationManager)getActivity().getSystemService(Context.LOCATION_SERVICE)).isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
currentPositionProvider.requestLocationUpdates(true, true, true);
}
SKMapViewHolder mapViewGroup = (SKMapViewHolder) getView().findViewById(R.id.map_surface_holder);
mapView = mapViewGroup.getMapSurfaceView();
mapView.setMapSurfaceListener(this);
mapView.getMapSettings().setFollowerMode(SKMapSettings.SKMapFollowerMode.NONE);
mapView.getMapSettings().setMapRotationEnabled(true);
mapView.getMapSettings().setMapZoomingEnabled(true);
mapView.getMapSettings().setMapPanningEnabled(true);
mapView.getMapSettings().setZoomWithAnchorEnabled(true);
mapView.getMapSettings().setInertiaRotatingEnabled(true);
mapView.getMapSettings().setInertiaZoomingEnabled(true);
mapView.getMapSettings().setInertiaPanningEnabled(true);
SKVersioningManager.getInstance().setMapUpdateListener(this);
mapView.centerMapOnPosition(new SKCoordinate( -118.123,34.123));
//launchRouteCalculation();
}
It seems that reportNewGPSPosition(this.currentPosition); does not work instantly.
I noticed that if I simply delay the call to centerMapOnCurrentPosition(), the map is centered correctly. There are two work arounds:
-set location manually:
mapView.centerMapOnPosition(new SKCoordinate( currentPosition.getLongitude(), currentPosition.getLatitude()));
-or create a delayed runnable to call to perform the centering at a future time:
android.os.Handler handler = new android.os.Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
mapView.centerMapOnCurrentPosition();
mapView.setZoom(10);
}
},100);
note: This issue only becomes evident because of the if statement I put in to only center the map on the first update of location. Another work around would be to put a use a counter and center map on the second time the location is updated.
Also if implementing this with the mentioned if statement, don't forget to consider accuracy; it would be bad to center when the location is not accurate, and not center on a future location update which IS accurate.
After long search on how to blink a marker i came up with this code in my project using google maps v2. Here is the code..
MyMarker = map.addMarker(new MarkerOptions().position(current_loc).title(address).snippet(city).
icon(BitmapDescriptorFactory.fromResource(R.drawable.bus4)));
markertimer = new Timer();
markertimer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
// TODO Auto-generated method stub
runOnUiThread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
blinkMarker();
}
});
}
}, 0, 500);
here the blinkMarker method..
private boolean blinkMarker(){
if(marker == true){
current.setVisible(true);
marker = false;
}
else if(marker == false){
current.setVisible(false);
marker = true;
}
return marker;
}
So the above is working fine and the marker is blinking , but my doubt is
will the above code use more memory for blinking?
and i'm using reverse geocoding for getting address, so not able to click the marker when blinking. Please suggest any other way to blink the marker?
Try replacing setVisible with setIcon. When doing that you need to prepare another drawable for icon with the same size as bus4.png, but with all pixels transparent.
Note there is a bug related to setIcon that causes info window to disappear.
As a side note a couple of problems in your code:
Using Timer is wasteful. It uses Thread, which you don't need. Consider using Handler instead.
You (probably) have a memory leak. Timer will not stop working when the Activity is destroyed. You need to cancel it if you don't do that already. If switching to Handler, rember to remove any Runnables or Messages you send.
Usually a Google map is more busy: with many markers. So, you can use Scheduledthreadpoolexecutor with runnable.
Your blinkMarket method could be shorter, like this:
'current.setVisible(!marker)'. I'm affraid that using Timer is not right approach. You can do the same using Handler with postDelayed. If the flashing of marker depends on some condition, that should be repeately checked, than flashing task should not be generated twice.
So in my current application, I want to zoom to a lat/long point, but also use the animateTo (or equivalent) to make sure the screen is properly centered. Currently I'm just doing something like this:
_mapController.animateTo(new GeoPoint(googleLat, googleLng));
// Smooth zoom handler
int zoomLevel = _mapView.getZoomLevel();
int targetZoomLevel = AppConstants.MAP_ZOOM_LEVEL_SWITCH;
long delay = 0;
while (zoomLevel++ < targetZoomLevel) {
handler.postDelayed(new Runnable() {
#Override
public void run() {
_mapController.zoomIn();
}
}, delay);
delay += 350; // Change this to whatever is good on the device
}
This kind of works, but what happens is that my thread starts, BEFORE the 'animate to' finishes, so it zooms in and then 'jumps' to display the correct center geoPoint. Is there a way to smoothly 'drill down' to a certain zoom level, as well as move to a geoPoint at the same time? Similar to how the official Googl eMaps application zooms to an address after you've searched for it is what I'm looking to do.
I would do something like:
_mapController.animateTo(new GeoPoint(googleLat,googleLong), new Runnable() {
public void run()
{
_mapController.zoomIn ();
}
});
This will achieve a pan-then-zoom effect. You can try your same logic inside the runnable to perform multi-step zoomIn's.
I am trying to setup a mylocationoverlay. Unfortunately, it is acting quite strangely. It works fine, except it does not appear until after I leave the MapActivity and come back in my application. Initially the map appears and there is a blue circle while it is getting a fine location. However, instead of resolving to a point, the circle just disappears.
My Code looks like this:
onResume() {
myLocation = new MyLocationOverlay(getActivity(), mp);
myLocation.enableMyLocation();
myLocation.runOnFirstFix(new Runnable(){
public void run() {
map.getOverlays().clear();
map.getOverlays().add(myLocation);
map.postInvalidate();
}
}
}
onPause() {
myLocation.disableMyLocation();
layout.removeView(map);
map = null;
}
Does anyone have any thoughts on what might be happening here? Since this is pretty much verbatim what all the examples online look like, I might add that I am testing this on a motorolla atrix running 2.3.4.
Edit : Let me take you through your code:
onResume() {
// First time: draw a circle somewhere here. There is no GPS fix yet, so no dot.
// Second time: The dot from the previous fix exists, so you get a circle and dot.
myLocation = new MyLocationOverlay(getActivity(), mp);
myLocation.enableMyLocation();
myLocation.runOnFirstFix(new Runnable(){
public void run() {
// First time: removes the circle and draws a dot.
// Second time: removes the circle and dot, and draw a new dot.
map.getOverlays().clear();
map.getOverlays().add(myLocation);
map.postInvalidate();
}
}
}
map.getOverlays().clear(); removes the circle
use remove() instead to remove the overlay(s) that you don't want, instead of clearing them all.
Remember to call map.invalidate(); whenever you need to force a redraw