I am using clustering for maker representation on map. But when user click on any cluster item. It bounds all latlng within cluster. this is my code:
#Override
public boolean onClusterClick(Cluster<MapMakerModel> cluster) {
String firstName = cluster.getItems().iterator().next().getTitle();
showSnackbar(cluster.getSize() + " (including " + firstName + ")");
// Zoom in the cluster. Need to create LatLngBounds and including all the cluster items
// inside of bounds, then animate to center of the bounds.
// Create the builder to collect all essential cluster items for the bounds.
LatLngBounds.Builder builder = LatLngBounds.builder();
for (ClusterItem item : cluster.getItems()) {
builder.include(item.getPosition());
}
// Get the LatLngBounds
final LatLngBounds bounds = builder.build();
// Animate camera to the bounds
try {
mMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 100));
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
so when this code is called it bound all latlng within that cluster.that is right but it also rotate camera from user angle to NS angle which I don't want to do.
How I can archive this??
Returned by CameraUpdateFactory.newLatLngBounds() method CameraUpdate object has
a bearing of 0 and a tilt of 0.
But you can get current (before set to bounds) bearing (probably tilt too) and set it AFTER camera animation. And not exactly after if you set duration of bounding box zoom animation to 0 and do all animation in next steps. Something like that:
...
// Get the LatLngBounds
final LatLngBounds bounds = builder.build();
// Get bearing and tilt
final float bearing = mGoogleMap.getCameraPosition().bearing;
final float tilt = mGoogleMap.getCameraPosition().tilt;
// Animate camera to the bounds
try {
mMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 100) ,0 /*duration of "set to bounds" animation */, new GoogleMap.CancelableCallback(){
#Override
public void onCancel() {
}
#Override
public void onFinish() {
mMap.animateCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition.Builder()
.bearing(bearing)
.bearing(tilt)
.build()), 1000 /*duration of "set bearing and tilt" animation */, null);
}
});
} catch (Exception e) {
e.printStackTrace();
}
...
Or you need to calculate your cluster LatLngBounds manually and use CameraUpdateFactory.newCameraPosition()
instead of CameraUpdateFactory.newLatLngBounds().
Related
I'm trying to show a ground overlay with markers on it to my users. I'm trying to restrain the view to only this image placed on the map. I want the user to only see the image as a ground overlay placed on the map and not be able to go to the surrounding map. And if they go over the edge, the gestures will be blocked.
I want something like this:
I don't want this:
show-only-ground-overlays-map-android or this:
I tried to set my map.setLatLngBoundsForCameraTarget() to my image bounds but the result is something like the previous image...
The next thing I tried is to set a bunch of LatLng objects all around the ground overlay and check with curScreen.contains(customMapDetectionPoints.get(LatLng Object)) to see if they appear on the viewport. It's does work but I can't stop the camera to go over the edge...
Here my test code so far :
private GroundOverlay groundOverlay;
private GoogleMap globalMap;
private final int DETECTION_POINTS_CUSTOM_MAP = 20;
private List<LatLng> customMapDetectionPoints = new ArrayList<>();
//Fully Working as suppose to
#Override
public void onMapReady(GoogleMap map) {
//Other Stuff...
LatLngBounds mapBounds = groundOverlay.getBounds();
map.setLatLngBoundsForCameraTarget(mapBounds);
globalMap = map;
LatLng northwest = new LatLng( mapBounds.northeast.latitude, mapBounds.southwest.longitude);
LatLng northeast = mapBounds.northeast;
LatLng southeast = new LatLng( mapBounds.southwest.latitude, mapBounds.northeast.longitude);
LatLng southwest = mapBounds.southwest;
//My ground overlay is rectangle so I don't need to follow a path or something like that
setDetectionPoints(northwest, southwest);
setDetectionPoints(northeast, southeast);
setDetectionPoints(northwest, northeast);
setDetectionPoints(southwest, southeast);
map.setOnCameraMoveStartedListener(new GoogleMap.OnCameraMoveStartedListener() {
#Override
public void onCameraMoveStarted(int i) {
LatLngBounds curScreen = globalMap.getProjection().getVisibleRegion().latLngBounds;
CameraPosition cameraPosition = globalMap.getCameraPosition();
for (int x =0;x<customMapDetectionPoints.size();x++) {
if (curScreen.contains(customMapDetectionPoints.get(x))) {
cancelMapMovement(cameraPosition);
Log.d("OUT", "Ground Overlay is outside viewport");
return;
} else {
globalMap.getUiSettings().setAllGesturesEnabled(true);
Log.d("IN", "Ground Overlay is inside viewport");
}
}
}
});
//Add 20 new location between two location
//Fully Working as suppose to
public void setDetectionPoints(LatLng fromPos, LatLng toPos) {
double pointLatitude = fromPos.latitude;
double pointLongitude = fromPos.longitude;
double addingValue;
if (fromPos.latitude == toPos.latitude) {
addingValue = (toPos.longitude - fromPos.longitude)/DETECTION_POINTS_CUSTOM_MAP;
for (int i = 0; i < DETECTION_POINTS_CUSTOM_MAP; i++) {
pointLongitude += addingValue;
LatLng pointsPos = new LatLng(pointLatitude, pointLongitude);
customMapDetectionPoints.add(pointsPos);
}
} else if (fromPos.longitude == toPos.longitude) {
addingValue = (toPos.latitude - fromPos.latitude)/DETECTION_POINTS_CUSTOM_MAP;
for (int i = 0; i < DETECTION_POINTS_CUSTOM_MAP; i++) {
pointLatitude += addingValue;
LatLng pointsPos = new LatLng(pointLatitude, pointLongitude);
customMapDetectionPoints.add(pointsPos);
}
}
}
//The problem is here!
public void cancelMapMovement(CameraPosition camPos ) {
//HOW CAN I STOP THE MOVEMENT OVER THE GROUND OVERLAY EDGE
//And make sure that the user dosen't see over the edge
globalMap.getUiSettings().setAllGesturesEnabled(false);
globalMap.moveCamera(CameraUpdateFactory.newCameraPosition(camPos));
}
At this point I think I have two possible solutions:
1- Only use the setLatLngBoundsForCameraTarget() function and set a offset or margin to the camera. But is it possible and will it work for my use case ?
2- Solve my camera restriction problem with the code already written
Thanks for helping! Suggest other solution if you find one!
I will provide more info if wanted.
I am working on map base application to animate marker. There are one marker which is updated against 30 sec interval from server. Marker always move to center of the Map ,so i close moveCamera marker but when marker move outside the map then marker is not come in map view. so i want to camera move when marker goes from map view
Before setting new position of the marker, check it's new position and current bounds of map view.
LatLng newPosition = new LatLng(...);
boolean contains = mMap.getProjection()
.getVisibleRegion()
.latLngBounds
.contains(newPosition);
if(!contains){
// MOVE CAMERA
}
// UPDATE MARKER POSITION
During the marker's each new position inside map view, marker moves(not
center of map)
Just If next position goes out of view, camera and marker will be centered.
Edit
I created a sample route to simulate each point on map periodically. Route gist
public class SampleRoute {
public static List<LatLng> GetPoints() {
return new ArrayList<>(Arrays.asList(
new LatLng(38.4670419, 27.1647131),
new LatLng(38.4667244, 27.1648277),
new LatLng(38.4666633, 27.1649079),
new LatLng(38.4665983, 27.1648022),
new LatLng(38.4665958, 27.1647843),
new LatLng(38.4665958, 27.1647843),
new LatLng(38.4665809, 27.1646429),
new LatLng(38.4665704, 27.1645506),
new LatLng(38.4665529, 27.1644067),
...
}
}
}
Then i create a method in sample activity that calculates current region's bounds and marker's X, Y points on this region. Activity gist
private void moveCamera(LatLng destination){
Projection projection = mMap.getProjection();
LatLngBounds bounds = projection.getVisibleRegion().latLngBounds;
int boundsTopY = projection.toScreenLocation(bounds.northeast).y;
int boundsBottomY = projection.toScreenLocation(bounds.southwest).y;
int boundsTopX = projection.toScreenLocation(bounds.northeast).x;
int boundsBottomX = projection.toScreenLocation(bounds.southwest).x;
int offsetY = (boundsBottomY - boundsTopY) / 10;
int offsetX = (boundsTopX - boundsBottomX ) / 10;
Point destinationPoint = projection.toScreenLocation(destination);
int destinationX = destinationPoint.x;
int destinationY = destinationPoint.y;
int scrollX = 0;
int scrollY = 0;
if(destinationY <= (boundsTopY + offsetY)){
scrollY = -(Math.abs((boundsTopY + offsetY) - destinationY));
}
else if(destinationY >= (boundsBottomY - offsetY)){
scrollY = (Math.abs(destinationY - (boundsBottomY - offsetY)));
}
if(destinationX >= (boundsTopX - offsetX)){
scrollX = (Math.abs(destinationX - (boundsTopX - offsetX)));
}
else if(destinationX <= (boundsBottomX + offsetX)){
scrollX = -(Math.abs((boundsBottomX + offsetX) - destinationX));
}
mMap.animateCamera(CameraUpdateFactory.scrollBy(scrollX, scrollY));
mMarker.setPosition(destination);
}
And then started to simulate points
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
moveCamera(mPoints.get(mCurrentPos));
if(++mCurrentPos < mPoints.size()){
mHandler.postDelayed(this, 1500);
}
}
}, 1500);
I tried and it's working well on me
So, if i understand you correctly and it works for you too then i could explain.
When marker location is updated you can animate the camera to the marker position. Following sample code may help you
LatLng definedLoc = new LatLng(latitudeValue, longitudeValue);
CameraPosition cameraPosition = new CameraPosition.Builder().target(definedLoc).zoom(13.0F).build();
map.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
You can animate the camera to the Marker position if the map bounds does not contain the marker using a GoogleMap.OnCameraChangeListener:
private Marker marker;
// ...
#Override
public void onCameraChange(final CameraPosition cameraPosition) {
ensureMarkerOnBounds();
}
private void ensureMarkerOnBounds() {
if (marker != null) {
if (!mMap.getProjection().getVisibleRegion().latLngBounds.contains(marker.getPosition())) {
mMap.animateCamera(CameraUpdateFactory.newLatLng(marker.getPosition()));
}
}
}
// This is the function that you use to move your marker
private void moveMarker (Marker marker) {
// ... Your code to move your marker
ensureMarkerOnBounds();
}
I want to create a method to see if the center of the camera co-ords is within the bounds of an area, if so then do something.
When i try this i am getting error: Operator ! cannot be applied to LatLngBounds.
I have also tried if(temp != new LatLngBounds(bounds1, bounds2)) but got the same error.
Here is my method
public GoogleMap.OnCameraChangeListener getCameraChangeListener()
{
return new GoogleMap.OnCameraChangeListener()
{
#Override
public void onCameraChange(CameraPosition cameraPosition)
{
LatLng temp = new LatLng(cameraPosition.target.latitude, cameraPosition.target.longitude);
//Northwest corner of bounds
LatLng bounds1 = new LatLng(-41.467668522, 173.03190229);
//Southwest corner of bounds
LatLng bounds2 = new LatLng(-40.095127348, 177.97574994);
LatLngBounds outside = new LatLngBounds(bounds1, bounds2);
//If the camera is within the bounds
if(temp != outside)
{
//do something
}
}
};
}
I think you mean northeast not northwest? Otherwise it would just be a line.
LatLngBounds has a Contains method which allows for
if (outside.contains(temp){
//do something
}
Outside is a bit of a misleading name for that variable though.
I am facing the problem that I need to update a map on the screen such that the all points from the route the user has taken is visible.
In the code below, I count the number of times I request a map update, but I noticed that sometimes the number of requests doesnt match the number of callbacks. So waiting for 'mapLoaded' to become 0 is not a good idea.
Therefore I have added a time limit of 10 seconds, but this is arbitrary and sometimes just not enough.
So, how can I know for sure that all map update has been completed?
private void adjustMapCompleteSO(LatLng from, LatLng to){//3.3.17 show all points for screenshot
double x1=(from.latitude+to.latitude)/2;
double x2=(from.longitude+to.longitude)/2;
LatLng del=new LatLng(x1,x2);
map.moveCamera(CameraUpdateFactory.newLatLng(del));
mapLoaded=0;
for(Polyline pol : allcrumbs){
List<LatLng> points = pol.getPoints();
for (LatLng point : points){
LatLngBounds.Builder builder = new LatLngBounds.Builder();
builder.include(point);
LatLngBounds bounds = builder.build();
int padding = 40; // offset from edges of the map in pixels
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, padding);
mapLoaded++;
map.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() {
public void onMapLoaded() {
mapLoaded--;
}
});
map.moveCamera(cu);
}
}
Date started = new Date();
while (mapLoaded !=0 && new Date().getTime() - started.getTime() < 10000){
try {//wait until map has loaded, but max 10 seconds
Thread.sleep(500);//wait half a second before tyring again
} catch (InterruptedException e) {}
}
}
Show all polylines on the map.
Create a builder
LatLngBounds.Builder builder = new LatLngBounds.Builder();
Iterate over all the points in the polylines sending them to lat long bounds builder.
for(Polyline pol : allcrumbs){
List<LatLng> points = pol.getPoints();
for (LatLng point : points){
// dude never initialize variables in a loop again
// its automatic fail for speed of execution.
// String never = "Do this in a loop";
// int padding = 40; // offset from edges of the map in pixels
builder.include(point);
}
}
Now move the camera
LatLngBounds bounds = builder.build();
int padding = 40; // offset from edges of the map in pixels
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, padding);
map.moveCamera(cu);
IDK what your doing in the maploaded callback so its not in the above code.
Tip: populate latlngbounds.builder as you create the polylines the simply move the camera when your finished loading the polylines.
LatLngBounds bounds = builder.build();
int padding = 40; // offset from edges of the map in pixels
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, padding);
map.moveCamera(cu);
Note: moving the camera along a route would be similar to your code but you would typically only update the camera when the camera is finished for each point.
I'm displaying a Google Maps API v2 MapView in an Android app, but it's curiously not updating properly in a consistent fashion. I'm using the GPS to update the map position (tried both LocationManager and LocationClient) and although the map moves to the position, about fifty percent of the time either the street name layer fails to update or there's a fuzzy/blurry section that fails to update--until I do a manual drag (scroll) of the map. Then the entire map updates instantly. I've stripped out a lot of the processing in the app to see if I was somehow preventing a refresh, but it didn't make a difference.
I inserted a mapView.invalidate() call in onCameraChange but that oddly seemed to make the problem occur more readily (although still not 100% of the time).
I am implementing all of the Activity callbacks as required by MapView.
Has anyone encountered a problem like this with Google Map API v2 on Android? If so, did you identify the cause and how did you solve it?
You have to let the map breathe so to speak.
Use animateCamera with a CancelableCallback then when the animation is completed you'll get a call back to onFinish() start the next animation.
public class KmlReader extends ActionBarActivity implements
CancelableCallback {
#Override
public void onFinish() {
startAnimation(); // start next map movement
}
#Override
public void onCancel() {
//Called when user interacts with the map while it is moving.
}
public void startAnimation(){
cameraPosition = mMap.getCameraPosition();
LatLng ll = new LatLng(expectedLocation.getLatitude(),
expectedLocation.getLongitude());
cb.zoom(cameraPosition.zoom)
// previous camera tilt
.tilt(cameraPosition.tilt)
// new expected destination
.target(ll)
// north up or heading view
.bearing((isHeading) ? bearing : 0f);
cameraPosition = cb.build();
CameraUpdate update = CameraUpdateFactory
.newCameraPosition(cameraPosition);
mMap.animateCamera(update, working_interval, this);
}
* Edit this is the code I'm working on now.*
it uses an asynctask for the calcuations. I've given it a walking test but haven't tested it in a vehicle.
private static CameraPosition currentCameraPosition;
private static com.google.android.gms.maps.model.CameraPosition.Builder cameraPositionBuilder;
private volatile CameraUpdate nextCameraUpdate;
// updates
private static final long UPDATE_INTERVAL = 2500;
// fastest
private static final int FASTEST_INTERVAL = 2500;
private static int working_interval = 5000;
private volatile boolean isAnimating;
// Define the callback method that receives location updates
#SuppressLint("NewApi")
#Override
public void onLocationChanged(Location location) {
Log.d("test", Boolean.toString(isAnimating) +" onlocation");
currentCameraPosition = mMap.getCameraPosition();
NewCameraUpdateTask newCameraUpdateTask = new NewCameraUpdateTask();
// This task must run async
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
newCameraUpdateTask.executeOnExecutor(
AsyncTask.THREAD_POOL_EXECUTOR, location);
} else {
newCameraUpdateTask.execute(location);
}
// speed display
setMetersPerSecond(location.getSpeed());
}
// create a newCameraUpdate to move the map with
private class NewCameraUpdateTask extends
AsyncTask<Location, Void, CameraUpdate> {
#Override
protected CameraUpdate doInBackground(Location... params) {
Location workingLocation = null;
CameraUpdate newCameraUpdate = null;
float bearing = 0f;
float speed = 0f;
for (Location mlocation : params) {
speed = mlocation.getSpeed();
// camera position is saved before the start of each animation.
LatLng ll;
if (!mlocation.hasBearing() || speed == 0) {
workingLocation = mlocation;
// previous bearing
} else {
// current bearing
bearing = mlocation.getBearing();
// calculate the age of the location
// atempt for animation to end a little bit past when
// the
// next
// location arrives.
// (location.getSpeed()m/s)(1/1000 interval seconds)(
// 1/1000
// km/m)
// (1/6371 radians/km) = radians/6371000000.0
double expectedDistance = working_interval / 6371000000.0
* speed;
// latitude in Radians
double currentLatitude = Math.toRadians(mlocation
.getLatitude());
// longitude in Radians
double currentlongitude = Math.toRadians(mlocation
.getLongitude());
double calcBearing = Math.toRadians(bearing);
// the camera position is needed so I can put in the
// previous camera bearing when the location has no
// bearing. This should prevent the map from
// zooming to north when the device stops moving.
// calculate the expected latitude and longitude based
// on
// staring
// location
// , bearing, and distance
double sincurrentLatitude = Math.sin(currentLatitude);
double coscurrentLatitude = Math.cos(currentLatitude);
double cosexpectedDistance = Math.cos(expectedDistance);
double sinexpectedDistance = Math.sin(expectedDistance);
double expectedLatitude = Math.asin(sincurrentLatitude
* cosexpectedDistance + coscurrentLatitude
* sinexpectedDistance * Math.cos(calcBearing));
double a = Math.atan2(
Math.sin(calcBearing) * sinexpectedDistance
* coscurrentLatitude,
cosexpectedDistance - sincurrentLatitude
* Math.sin(expectedLatitude));
double expectedLongitude = currentlongitude + a;
expectedLongitude = (expectedLongitude + PI3) % PI2 - PI;
// convert to degrees for the expected destination
double expectedLongitudeDestination = Math
.toDegrees(expectedLongitude);
double expectedLatitudeDestination = Math
.toDegrees(expectedLatitude);
mlocation.setLatitude(expectedLatitudeDestination);
mlocation.setLongitude(expectedLongitudeDestination);
workingLocation = mlocation;
}
break;
}
if (workingLocation != null) {
if (workingLocation.hasBearing()) {
bearing = workingLocation.getBearing();
} else {
bearing = currentCameraPosition.bearing;
}
LatLng ll = new LatLng(workingLocation.getLatitude(),
workingLocation.getLongitude());
cameraPositionBuilder.zoom(currentCameraPosition.zoom)
// previous camera tilt
.tilt(currentCameraPosition.tilt)
// new expected destination
.target(ll)
// north up or heading view
.bearing((isHeading) ? bearing : 0f);
newCameraUpdate = CameraUpdateFactory
.newCameraPosition(cameraPositionBuilder.build());
}
return newCameraUpdate;
}
#Override
protected void onPostExecute(CameraUpdate result) {
Log.d("test", Boolean.toString(isAnimating) + " onPostExecute");
if (result != null) {
nextCameraUpdate = result;
// stop the currently playing animation
// there is a new one ready to start
if (isAnimating) {
if (mMap != null) {
mMap.stopAnimation();
}
}
// start the next animation
startAnimation();
Log.d("test", Boolean.toString(isAnimating) +" onPostExecuteComplete");
}
}
}
// called when map animation has been canceled
#Override
public void onCancel() {
Log.d("test", Boolean.toString(isAnimating) +" oncancel");
isAnimating = false;
}
#Override
public void onFinish() {
Log.d("test", Boolean.toString(isAnimating) +" onfinish");
isAnimating = false;
startAnimation();
// call to start saved animation.
}
private void startAnimation() {
Log.d("test", Boolean.toString(isAnimating) +" startAnimation");
if (action_track) {
if (isAnimating) {
return;
}
if (nextCameraUpdate == null) {
return;
}
// abort if animating
isAnimating = true;
CameraUpdate animateCameraUpdate = nextCameraUpdate;
nextCameraUpdate = null;
mMap.animateCamera(animateCameraUpdate, working_interval, this);
Log.d("test", Boolean.toString(isAnimating) +" startanimateCamera");
}
}
In case someone is having this issue, the key is to keep the animateCamera call's animation duration less than the frequency with which you are calling the animateCamera method.
For example if you call animateCamera every 1000ms set tha animate camera duration to be less than that.
map.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, ZOOM_LEVEL), 900, null)
If your animateCamera call is not being called at a constant time, then danny117's answer to use the callback to trigger the next camera update will work perfectly.
Inspired from danny117's solution, I found a simpler solution.
I set the location request update for each 5 millisecond and I set the animate camera duration to be 2.5 ms. Problem solved