Summary
I need to be able to move from my current mapview to a latitude/longitude span. I want this move to happen in an animated sort of way (similar to how animateTo works). I need to go from my current view, and zoom in or out to end up at the target latitude/longitude span. Below is the code I'm using to do this when I use zoomLevels. But I have to use spans, not zoom levels to accomplish this.
[Edit]
Here's what I'm doing for animateTo. I need the equivalent of this, but using Lat/Lng Span to end up at my final view (i need to be zoomed and centered, showing the lat/lng span provided). As you'll notice, i'm using targetZoomLevel and currentZoomLevel to determine how much zooming to do... I need to be using the lat/lng span instead.
public static void smoothZoom(final MapController controller,
final int currentZoomLevel, final int targetZoomLevel,
int latitude, int longitude) {
final Handler handler = new Handler();
controller.animateTo(new GeoPoint(latitude, longitude), new Runnable() {
public void run() {
int currentZoom = currentZoomLevel;
int zoomTarget = targetZoomLevel;
// Smooth zoom handler
// int zoomLevel = _mapView.getZoomLevel();
// int targetZoomLevel = targetLevel;
long delay = 0;
if (zoomTarget > currentZoom) {
while (currentZoom++ < zoomTarget) {
handler.postDelayed(new Runnable() {
#Override
public void run() {
controller.zoomIn();
}
}, delay);
delay += 150; // Change this to whatever is good on the
// device
}
}
}
});
}
[Edit]
I really need this code for my application now, there is no way around it. First person to help me get working code gets 250 rep.
This has worked for me:
MapController.setCenter(GeoPoint);
You can get MapController from:
MapView.getController();
And get a GeoPoint from:
new GeoPoint( (int) ( LAT * 1e6 ) , (int) ( LONG * 1e6 ) );
Is this the kind of solution you're looking for?
public static void zoomSpan(final MapView mapView, int northernLat, int southernLat, int easternLon, int westernLon) {
final int latSpan = northernLat - southernLat;
final int lonSpan = easternLon - westernLon;
latCenter = southernLat + latSpan / 2;
lonCenter = westernLon + lonSpan / 2;
final MapController controller = mapView.getController();
final Handler handler = new Handler();
final Runnable zoomRunnable = new Runnable() {
public void run() {
int viewLatSpan = mapView.getLatitudeSpan();
int viewLonSpan = mapView.getLongitudeSpan();
boolean actionTaken = false;
if (mapView.getZoomLevel() > 1 && (latSpan > viewLatSpan || lonSpan > viewLonSpan)) {
controller.zoomOut();
actionTaken = true;
}
if (mapView.getZoomLevel() < mapView.getMaxZoomLevel() && latSpan * 2 <= viewLatSpan && lonSpan * 2 <= viewLonSpan) {
controller.zoomIn();
actionTaken = true;
}
if (actionTaken)
handler.post(zoomRunnable);
}
};
controller.animateTo(latCenter, lonCenter, zoomRunnable);
}
In my map application I needed to implement a location search feature. If location found it moves to that location, add an overlay and zooms to a predefined level.
I used following code for this purpose:
final int SEARCH_ZOOM_LEVEL = 7;
controller = mMapView.getController();
controller.setZoom(SEARCH_ZOOM_LEVEL );
pointToLocation(s); // s is the location
pointToLocation() was implements like this:
private void pointToLocation(String s) {
Geocoder gc = new Geocoder(MapDemo.this, Locale.getDefault());
try {
List<Address> addresses = gc.getFromLocationName(
s, 5);
String add = "";
if (addresses.size() > 0) {
point = new GeoPoint(
(int) (addresses.get(0).getLatitude() * 1E6),
(int) (addresses.get(0).getLongitude() * 1E6));
controller.animateTo(point);
}
} catch (IOException e) {
Toast.makeText(MapDemo.this, "Location not found", Toast.LENGTH_LONG).show();
}
}
The usage of the function you're looking at is zoomToSpan(int latSpanE6, int lonSpanE6).
You have the current span (or can get it easily) and the target span (as you said you know it), so why don't you simply start calling zoomToSpan on a delay , as you incrementally move latSpanE6 and longSpanE6 towards the target. It shouldn't be that difficult. You already have most of the code there, but instead of zoomIn while checking the zoom level, you would zoomToSpan while checking if latSpanE6 and longSpanE6 have reached your target span.
Won't bother coding it since you can do it yourself ;)
Related
I am building a car navigation application in Xamarin.Android (native not forms). I had read a lot, and follow instructions I found on how to add the map in the application and also how to use the location services, either with the FusedLocationProvider or the NavigationManager. I decided to use the NavigationManager since it looks more accurate because of the number of decimal digits (14) in the LatLng. All good up to now. I am testing my app and while the marker and the map are moving, in order to follow the car's direction, the map is blurred (see the attached pictures), it looks like it does not have the appropriate time to get refreshed.
What I am doing in order to move the camera and the marker?
1. OnLocationChanged from the LocationManager I am calling the UpdateCameraBearing function
internal static void UpdateCameraBearing(float bearing, Location newLocation)
{
if (Map == null)
return;
// Save current zoom
int originalZoom = (int)Map.CameraPosition.Zoom;
if (!startingZoom.HasValue)
{
startingZoom = originalZoom = MapZoomDef;
}
LastLocation = newLocation;
LatLng newPosition = new LatLng(newLocation.Latitude, newLocation.Longitude);
CameraPosition camPos = new CameraPosition(newPosition, originalZoom != MapZoomDef ? originalZoom : MapZoomDef, 0, bearing);
try
{
Map.AnimateCamera(CameraUpdateFactory.NewCameraPosition(camPos));
UpdateMarkerPosition(newPosition);
}
catch (Exception ex)
{
Log.Error(TAG, ex.Message);
}
}
This function animates the camera to the new position and also updates the marker position using the UpdateMarkerPosition function as follows
private static void UpdateMarkerPosition(LatLng newPosition)
{
if (null == MapMarker)
{
SetNewMarker(newPosition);
}
else
{
//positionMarker.Visible = true;
//positionMarker.Position = newPosition;
AnimateMarker(newPosition, new LatLngInterpolatorSpherical());
}
}
private static void SetNewMarker(LatLng myPosition)
{
MarkerOptions markerOptions = new MarkerOptions();
markerOptions.SetPosition(myPosition);
markerOptions.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Mipmap.ic_car));
MapMarker = Map.AddMarker(markerOptions);
}
private static void AnimateMarker(LatLng finalPosition, LatLngInterpolatorSpherical latLngInterpolator)
{
Android.OS.Handler handler = new Android.OS.Handler();
long start = Android.OS.SystemClock.UptimeMillis();
LatLng startPosition = MapMarker.Position;
Android.Views.Animations.AccelerateDecelerateInterpolator interpolator = new Android.Views.Animations.AccelerateDecelerateInterpolator();
float durationInMs = 1000;
Java.Lang.Runnable mUpdateGeneration = null;
mUpdateGeneration = new Java.Lang.Runnable(() => {
long elapsed;
float t;
float v;
// Calculate progress using interpolator
elapsed = Android.OS.SystemClock.UptimeMillis() - start;
t = elapsed / durationInMs;
v = interpolator.GetInterpolation(t);
MapMarker.Position = latLngInterpolator.Interpolate(v, startPosition, finalPosition);
// Repeat till progress is complete.
if (t < 1)
{
// Post again 16ms later.
handler.PostDelayed(mUpdateGeneration, 16);
}
});
handler.Post(mUpdateGeneration);
}
The UpdateMarkerPosition function checks if the marker exists and if not (this is the first time) it creates it, otherwise it calls the AnimateMarker function where I am using an Interpolator in order to simulate a smooth marker movement.
Last but not least the LocationManager.RequestLocationUpdates is configured with 1-second minimum time and 1-meter minimum Distance.
I will appreciate any suggestions on how to solve the problem...
enter image description here
enter image description here
I'm implementing the animations for the group of cars(Markers) to rotate at the same time in google map v2.
so that i need to code my animation part inside handler.post() method.
By doing this the animation part(handler.post() method) is running for only one marker.
i have coded handler.post() method inside the for loop. It runs only for the first time which means only one marker is rotating. after that it is not working. My code is as follows.
private void animateCarsInMarkers(final MarkerOptions mark, final long bearing, final LatLng startPosition, final int position){
final Handler handler = new Handler();
final long start = SystemClock.uptimeMillis();
final long duration = 3000;
final Interpolator interpolator = new LinearInterpolator();
final Marker marker = mGoogleMap.addMarker(mark);
final float rotationValue = Float.parseFloat(String.valueOf(bearing));
try {
if(tempCarsArray != null && !tempCarsArray.isEmpty()){
sLongitude = tempCarsArray.get(position).getDouble(LONGITUDE);
sLatitude = tempCarsArray.get(position).getDouble(LATITUDE);
sBearing = tempCarsArray.get(position).getLong("Bearing");
final double dLongitude = startPosition.longitude;
final double dLatitude = startPosition.latitude;
handler.post(new Runnable() {
#Override
public void run() {
long elapsed = SystemClock.uptimeMillis() - start;
float time = interpolator.getInterpolation((float) elapsed / duration);
double lng = time * dLongitude + (1 - time) * sLongitude;
double lat = time * dLatitude + (1 - time) * sLatitude;
float rotationValue = time * dbearing + (1-time) * sBearing;
marker.setRotation((-rotationValue > 180) ? (rotationValue / 2) : rotationValue);
marker.setPosition(new LatLng(lat, lng));
if (time < 1.0) {
handler.postDelayed(this, 16);
}
}
});
tempCarsArray.clear();
} else {
marker.setPosition(startPosition);
marker.setRotation(-rotationValue > 180 ? rotationValue / 2 : rotationValue);
}
}catch (JSONException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
This is the method which i called inside for loop. But it runs only for the first time in loop. Later it is not working. So only one marker is animated among the group of markers. My for loop is as follows :
for(int i=0; i<size; i++){
animateCarsInMarkers(mark, bearing, latLng, i);
}
This loop runs only when the value of i=0 and it wont runs again.
Thanks in advance.
If your 'for loop' ran only once, it means that your "for loop condition part", i<size for this instance, has already satisfied the condition. What I would suggest is for you to actually log "size" and check its value.
If you're looking for the size of an array, use tempCarsArray.length like:
for(int i=0; i < tempCarsArray.length; i++){
animateCarsInMarkers(mark, bearing, latLng, i);
}
To check if size is causing the problem, try this.
If you know the actual number of markers you're expecting to rotate, try to substitute with an integer for now, like:
//if you're expecting 5 markers
for(int i=0; i < 5 ; i++){
animateCarsInMarkers(mark, bearing, latLng, i);
}
If all markers did rotate, it means your size variable has only a value of 1.
I need to make animated movement if the airplane marker along stored coordinates in ArrayList collection. I've found the solution, how to do move marker from one coordinate to another. Here is the code of the method, which moves it:
private void animateMarker(final Marker plane,
final LatLng toPosition,
final boolean hideMarker,
int currentIndex) {
final Handler handler = new Handler();
final long start = SystemClock.uptimeMillis();
Projection proj = gMap.getProjection();
Point startPoint = proj.toScreenLocation(plane.getPosition());
final LatLng startLatLng = proj.fromScreenLocation(startPoint);
final long duration = 2500;
final Interpolator interpolator = new LinearInterpolator();
handler.post(new Runnable() {
#Override
public void run() {
long elapsed = SystemClock.uptimeMillis() - start;
float t = interpolator.getInterpolation((float) elapsed / duration);
double lat = t * toPosition.latitude + (1 - t) * startLatLng.latitude;
double lng = t * toPosition.longitude + (1 - t) * startLatLng.longitude;
plane.setPosition(new LatLng(lat, lng));
if (t < 1.0) {
handler.postDelayed(this, 16);
} else {
if (hideMarker) {
plane.setVisible(false);
} else {
plane.setVisible(true);
}
}
}
});
//Call the method which will start another animateMarker()
// after this thread is finished
currentIndex++;
if(currentIndex < theRoute.size()-1){
movePlane(plane, currentIndex);
}
}
This method works perfectly, when I'm trying to move the object from one coordinate to another, but I need to move it along the collection of coordinates. As you recognized, in the end of the code I increment the "currentIndex" value, and call movePlane method, which accepts the instance of the marker and new index value. Here the chaos starts, but let me show you the code:
private void movePlane(Marker plane, int index){
if(currentCoordIndex < theRoute.size()){
animateMarker(plane, theRoute.get(index), false, index);
}
}
So, the plane marker is moving chaotically back and forth from coordinate to another and it is not acceptable. I suppose, that new thread is called, when the other is not finished. Maybe, solution is quite simple, but I really don't know how to do, what I expect.
Also i need to rotate the marker towards the next point, could you help me with that?
I'm creating a cluster feature for maps (v2). I group location into clusters and then display the clusters as custom markers:
This works great, but I would like to create an animation when clusters get created and split up. I successfully did this with iOS by creating a UIView Animation on the markers (annotations). I couldn't find any code / hints online for android.
I managed to get a simple ImageView as overlay to resemble a cluster and then use a TranslateAnimation to get the desired animation. At the end I removed this view and added the marker.
Is there a better way to animate a marker?
You're on the right track by animating the Marker object rather than adding and removing Views in front of the GoogleMap, but you can get better performance if you use an Animator object to animate the Marker.
With the Handler and delayed Runnable approach, you're effectively hard coding a target frame rate. If you post the Runnable with too low of a delay, your animation will take longer to execute. If too high, the frame rate will be too slow, and it'll look choppy even on powerful devices. The advantage to using an Animator over a Handler and delayed Runnable is that it will only call onAnimationUpdate() to draw the next frame as often as the system can handle.
In my clustering library, Clusterkraf, I used an ObjectAnimator (from NineOldAndroids for backwards compatibility) to animate cluster transitions when changing zoom level. It can smoothly animate around 100 markers on my Galaxy Nexus.
Below is a snippet with an overview of how to make that work.
class ClusterTransitionsAnimation implements AnimatorListener, AnimatorUpdateListener {
private ObjectAnimator animator;
private AnimatedTransitionState state;
private ClusterTransitions transitions;
private Marker[] animatedMarkers;
void animate(ClusterTransitions transitions) {
if (this.state == null) {
Options options = optionsRef.get();
Host host = hostRef.get();
if (options != null && host != null) {
this.state = new AnimatedTransitionState(transitions.animated);
this.transitions = transitions;
animator = ObjectAnimator.ofFloat(this.state, "value", 0f, 1f);
animator.addListener(this);
animator.addUpdateListener(this);
animator.setDuration(options.getTransitionDuration());
animator.setInterpolator(options.getTransitionInterpolator());
host.onClusterTransitionStarting();
animator.start();
}
}
}
#Override
public void onAnimationStart(Animator animator) {
// Add animatedMarkers to map, omitted for brevity
}
#Override
public void onAnimationUpdate(ValueAnimator animator) {
if (state != null && animatedMarkers != null) {
LatLng[] positions = state.getPositions();
for (int i = 0; i < animatedMarkers.length; i++) {
animatedMarkers[i].setPosition(positions[i]);
}
}
}
}
The code below is correct, but I would puntualize something. I would use 16 ms == 60fps. The human eye can't distinguis less than 16 ms, so:
final LatLng target = NEW_LOCATION;
final long duration = 400;
final Handler handler = new Handler();
final long start = SystemClock.uptimeMillis();
Projection proj = map.getProjection();
Point startPoint = proj.toScreenLocation(marker.getPosition());
final LatLng startLatLng = proj.fromScreenLocation(startPoint);
final Interpolator interpolator = new LinearInterpolator();
handler.post(new Runnable() {
#Override
public void run() {
long elapsed = SystemClock.uptimeMillis() - start;
float t = interpolator.getInterpolation((float) elapsed / duration);
double lng = t * target.longitude + (1 - t) * startLatLng.longitude;
double lat = t * target.latitude + (1 - t) * startLatLng.latitude;
marker.setPosition(new LatLng(lat, lng));
if (t < 1.0) {
// Post again 16ms later == 60 frames per second
handler.postDelayed(this, 16);
} else {
// animation ended
}
}
});
I found a solution that works:
final LatLng target = NEW_LOCATION;
final long duration = 400;
final Handler handler = new Handler();
final long start = SystemClock.uptimeMillis();
Projection proj = map.getProjection();
Point startPoint = proj.toScreenLocation(marker.getPosition());
final LatLng startLatLng = proj.fromScreenLocation(startPoint);
final Interpolator interpolator = new LinearInterpolator();
handler.post(new Runnable() {
#Override
public void run() {
long elapsed = SystemClock.uptimeMillis() - start;
float t = interpolator.getInterpolation((float) elapsed / duration);
double lng = t * target.longitude + (1 - t) * startLatLng.longitude;
double lat = t * target.latitude + (1 - t) * startLatLng.latitude;
marker.setPosition(new LatLng(lat, lng));
if (t < 1.0) {
// Post again 10ms later.
handler.postDelayed(this, 10);
} else {
// animation ended
}
}
});
It's a bit slow with about 20 simultaneously, maybe there is a better way?
I am trying to get the bounds of my mapview but I had the problem that the latitude is always set to 0 and longtitude is always set to 360*1E6. According to this link, that is because it will only offer the right coordinates when the map is fully loaded:
Mapview getLatitudeSpan and getLongitudeSpan not working
Now, I am totally confused about the solution, I made this method which I call from the onCreate of my mainactivity (the mapview):
public int[][] getBounds()
{
GeoPoint center = this.mapView.getMapCenter();
int latitudeSpan = this.mapView.getLatitudeSpan();
int longtitudeSpan = this.mapView.getLongitudeSpan();
int[][] bounds = new int[2][2];
bounds[0][0] = center.getLatitudeE6() + (latitudeSpan/2);
bounds[0][1] = center.getLongitudeE6() + (longtitudeSpan/2);
bounds[1][0] = center.getLatitudeE6() - (latitudeSpan/2);
bounds[1][1] = center.getLongitudeE6() - (longtitudeSpan/2);
return bounds;
}
How do I make this wait for the mapview to load? I've looked in the API for postDelayed, but I cannot get it to work.
Forgive me if I am being stupid o.o'
The correct way to create a "Timer" in android is to utilize android.os.Handler:
private Handler updateHandler = new Handler();
updateHandler.postDelayed(waitForMapTimeTask, TIME_TO_WAIT_IN_MS);
private Runnable waitForMapTimeTask = new Runnable() {
public void run() {
getBounds();
// Read the bounds to see if something reasonable is returned
if (!mapIsLoaded) {
updateHandler.postDelayed(waitForMapTimeTask, TIME_TO_WAIT_IN_MS);
}
}
};