Related
I've added GroundOverlay to map and want to limit scrolling and zooming within this area.
How to limit scrolling within some bounds on android google maps?
Is it possible to get instantly motion points from MapFragment?
Please, help me.
Constraining the camera has (finally!) been added as a feature as part of the release of Google Play Services 9.4 -- you can call setLatLngBoundsForCameraTarget(LatLngBounds bounds) to set the allowed panning area.
// Create a LatLngBounds that includes the city of Adelaide in Australia.
final LatLngBounds ADELAIDE = new LatLngBounds(
new LatLng(-35.0, 138.58), new LatLng(-34.9, 138.61));
// Constrain the camera target to the Adelaide bounds.
mMap.setLatLngBoundsForCameraTarget(ADELAIDE);
You can find a thorough explanation in the documentation: Restricting the user's panning to a given area and a sample activity in GitHub.
May be it is too late, but here is my solution:
Disabled built-in GoogleMap's gestures.
Added custom gestures (for scrolling, fling and scaling).
Checking for allowed area when processing events.
Set bounds/zoom manually with standard Map's functions.
And here is my example:
[UPDATED]
There was a problem - when touch events were recieved before map was initialized.
check null values in onInterceptTouchEvent
Also I have discovered that my solution is slightly slowly than build-in function.
import android.content.Context;
import android.graphics.Point;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMapOptions;
import com.google.android.gms.maps.MapView;
import com.google.android.gms.maps.MapsInitializer;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.VisibleRegion;
public class RestrictedMapView extends MapView {
public static float MAX_ZOOM = 20;
public static float MIN_ZOOM = 5;
public static float MIN_ZOOM_FOR_FLING = 7;
public static double MAX_LONGITUDE = 183.61;
public static double MIN_LONGITUDE = 159.31;
public static double MAX_LATITUDE = -32.98;
public static double MIN_LATITUDE = -53.82;
public static double DEF_LATITUDE = -41.78;
public static double DEF_LONGITUDE = 173.02;
public static float DEF_ZOOM = 7;
private Handler mHandler = new Handler();
private Context mContext;
private VisibleRegion mLastCorrectRegion = null;
private boolean mIsInAnimation = false;
public RestrictedMapView(Context c, AttributeSet a, int o) {
super(c, a, o);
init(c);
}
public RestrictedMapView(Context c, AttributeSet a) {
super(c, a);
init(c);
}
public RestrictedMapView(Context c) {
super(c);
init(c);
}
public RestrictedMapView(Context c, GoogleMapOptions o) {
super(c, o);
init(c);
}
private GestureDetector mGestureDetector = null;
private GestureDetector.SimpleOnGestureListener mGestudeListener =
new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (mIsInAnimation) return false;
GoogleMap map = getMap();
LatLng target = map.getCameraPosition().target;
Point screenPoint = map.getProjection().toScreenLocation(target);
Point newPoint = new Point(screenPoint.x + (int)distanceX, screenPoint.y + (int)distanceY);
LatLng mapNewTarget = map.getProjection().fromScreenLocation(newPoint);
CameraUpdate update = CameraUpdateFactory.newLatLngZoom(
mapNewTarget,map.getCameraPosition().zoom);
tryUpdateCamera(update, 0);
return true;
}
#Override
public boolean onFling (MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (mIsInAnimation) return false;
GoogleMap map = getMap();
double zoom = map.getCameraPosition().zoom;
if (zoom < MIN_ZOOM_FOR_FLING)
return false;
int velocity = (int) Math.sqrt(velocityX * velocityX + velocityY * velocityY);
if (velocity < 500) return false;
double k1 = 0.002d; /*exipemental*/
double k2 = 0.002d;/*exipemental*/
LatLng target = map.getCameraPosition().target;
Point screenPoint = map.getProjection().toScreenLocation(target);
Point newPoint = new Point(screenPoint.x - (int)(velocityX * k1 * zoom * zoom/*exipemental*/),
screenPoint.y - (int)(velocityY * k1 * zoom * zoom/*exipemental*/));
LatLng mapNewTarget = map.getProjection().fromScreenLocation(newPoint);
CameraUpdate update = CameraUpdateFactory.newLatLngZoom(
mapNewTarget,map.getCameraPosition().zoom);
tryUpdateCamera(update, (int)(velocity * k2 * zoom * zoom) /*exipemental*/);
return true;
}
};
private ScaleGestureDetector mScaleGestureDetector = null;
private ScaleGestureDetector.SimpleOnScaleGestureListener mScaleGestudeListener =
new ScaleGestureDetector.SimpleOnScaleGestureListener() {
#Override
public boolean onScale (ScaleGestureDetector detector) {
if (mIsInAnimation) return false;
GoogleMap map = getMap();
double zoom = map.getCameraPosition().zoom;
double k = 1d / detector.getScaleFactor();
int x = (int) detector.getFocusX();
int y = (int) detector.getFocusY();
LatLng mapFocus = map.getProjection().
fromScreenLocation(new Point(x, y));
LatLng target = map.getCameraPosition().target;
zoom = zoom + Math.log(detector.getScaleFactor()) / Math.log(2d);
if (zoom < MIN_ZOOM)
if (zoom == MIN_ZOOM) return false;
else zoom = MIN_ZOOM;
if (zoom > MAX_ZOOM)
if (zoom == MAX_ZOOM) return false;
else zoom = MAX_ZOOM;
double dx = norm(mapFocus.longitude) - norm(target.longitude);
double dy = mapFocus.latitude - target.latitude;
double dk = 1d - 1d / k;
LatLng newTarget = new LatLng(target.latitude - dy * dk,
norm(target.longitude) - dx * dk);
CameraUpdate update = CameraUpdateFactory.newLatLngZoom(newTarget, (float) zoom);
tryUpdateCamera(update, 0);
return true;
}
};
private void tryUpdateCamera(CameraUpdate update, int animateTime) {
GoogleMap map = getMap();
final VisibleRegion reg = map.getProjection().getVisibleRegion();
if (animateTime <= 0) {
map.moveCamera(update);
checkCurrentRegion(reg);
} else {
mIsInAnimation = true;
map.animateCamera(update, animateTime, new GoogleMap.CancelableCallback() {
#Override
public void onFinish() {
mIsInAnimation = false;
checkCurrentRegion(reg);
}
#Override
public void onCancel() {
mIsInAnimation = false;
checkCurrentRegion(reg);
}
});
}
}
private void checkCurrentRegion(VisibleRegion oldReg) {
GoogleMap map = getMap();
VisibleRegion regNew = map.getProjection().getVisibleRegion();
if (checkBounds(regNew)) {
mLastCorrectRegion = regNew;
} else {
if (mLastCorrectRegion != null)
oldReg = mLastCorrectRegion;
mIsInAnimation = true;
map.animateCamera(CameraUpdateFactory.newLatLngBounds(
oldReg.latLngBounds, 0),
200, new GoogleMap.CancelableCallback() {
#Override
public void onFinish() {
mIsInAnimation = false;
}
#Override
public void onCancel() {
mIsInAnimation = false;
}
});
}
}
/**
*
*
* #param lonVal
* #return
*/
private double norm(double lonVal) {
while (lonVal > 360d) lonVal -= 360d;
while (lonVal < -360d) lonVal += 360d;
if (lonVal < 0) lonVal = 360d + lonVal;
return lonVal;
}
private double denorm(double lonVal) {
if (lonVal > 180d) lonVal = -360d + lonVal;
return lonVal;
}
private boolean checkBounds(VisibleRegion reg) {
double left = Math.min(
Math.min(norm(reg.farLeft.longitude), norm(reg.nearLeft.longitude)),
Math.min(norm(reg.farRight.longitude), norm(reg.nearRight.longitude)));
double right = Math.max(
Math.max(norm(reg.farLeft.longitude), norm(reg.nearLeft.longitude)),
Math.max(norm(reg.farRight.longitude), norm(reg.nearRight.longitude)));
double top = Math.max(
Math.max(reg.farLeft.latitude, reg.nearLeft.latitude),
Math.max(reg.farRight.latitude, reg.nearRight.latitude));
double bottom = Math.min(
Math.min(reg.farLeft.latitude, reg.nearLeft.latitude),
Math.min(reg.farRight.latitude, reg.nearRight.latitude));
boolean limitBounds = left < MIN_LONGITUDE || right > MAX_LONGITUDE ||
bottom < MIN_LATITUDE || top > MAX_LATITUDE;
return !limitBounds;
}
private void init(Context c) {
try {
MapsInitializer.initialize(c);
} catch (GooglePlayServicesNotAvailableException e) {
e.printStackTrace();
}
mContext = c;
mHandler.post(new Runnable() {
#Override
public void run() {
GoogleMap map = getMap();
if (map != null) {
getMap().getUiSettings().setZoomControlsEnabled(false);
map.getUiSettings().setAllGesturesEnabled(false);
map.moveCamera(CameraUpdateFactory.newLatLngZoom(
new LatLng(DEF_LATITUDE, DEF_LONGITUDE), DEF_ZOOM));
mLastCorrectRegion = map.getProjection().getVisibleRegion();
mGestureDetector = new GestureDetector(mContext, mGestudeListener);
mScaleGestureDetector = new ScaleGestureDetector(mContext, mScaleGestudeListener);
} else mHandler.post(this);
}
});
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mGestureDetector != null) mGestureDetector.onTouchEvent(event);
if (mScaleGestureDetector != null) mScaleGestureDetector.onTouchEvent(event);
return super.onInterceptTouchEvent(event);
}
}
Definition within my xml-layout of fragment:
<com.package....RestrictedMapView
android:id="#+id/mapview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
In xml file it is possible to define custom zoom/location buttons and set click listeners for manual manipulating camera (in this case you have to check MAX_ZOOM and MIN_ZOOM, and check if the current location is within the allowed bounds).
Too bad that google doesn't let us intercept and block the user, i found that MaciejGórski's answer is the one that suits best to my needs.
I wanted to share my solution (Based on his answer) with you.
First i defined the bounds and max/min zoom:
private final LatLngBounds BOUNDS = new LatLngBounds(new LatLng(41.8138,12.3891), new LatLng(41.9667, 12.5938));
private final int MAX_ZOOM = 18;
private final int MIN_ZOOM = 14;
Then i created this little function to test if current camera bounds are outside of max bounds and return the difference in latitude and longitude.
/**
* Returns the correction for Lat and Lng if camera is trying to get outside of visible map
* #param cameraBounds Current camera bounds
* #return Latitude and Longitude corrections to get back into bounds.
*/
private LatLng getLatLngCorrection(LatLngBounds cameraBounds) {
double latitude=0, longitude=0;
if(cameraBounds.southwest.latitude < BOUNDS.southwest.latitude) {
latitude = BOUNDS.southwest.latitude - cameraBounds.southwest.latitude;
}
if(cameraBounds.southwest.longitude < BOUNDS.southwest.longitude) {
longitude = BOUNDS.southwest.longitude - cameraBounds.southwest.longitude;
}
if(cameraBounds.northeast.latitude > BOUNDS.northeast.latitude) {
latitude = BOUNDS.northeast.latitude - cameraBounds.northeast.latitude;
}
if(cameraBounds.northeast.longitude > BOUNDS.northeast.longitude) {
longitude = BOUNDS.northeast.longitude - cameraBounds.northeast.longitude;
}
return new LatLng(latitude, longitude);
}
Then the Handler that controls the overscroll (And overzoom) limiting it every 100ms.
/**
* Bounds the user to the overlay.
*/
private class OverscrollHandler extends Handler {
#Override
public void handleMessage(Message msg) {
CameraPosition position = mMap.getCameraPosition();
VisibleRegion region = mMap.getProjection().getVisibleRegion();
float zoom = 0;
if(position.zoom < MIN_ZOOM) zoom = MIN_ZOOM;
if(position.zoom > MAX_ZOOM) zoom = MAX_ZOOM;
LatLng correction = getLatLngCorrection(region.latLngBounds);
if(zoom != 0 || correction.latitude != 0 || correction.longitude != 0) {
zoom = (zoom==0)?position.zoom:zoom;
double lat = position.target.latitude + correction.latitude;
double lon = position.target.longitude + correction.longitude;
CameraPosition newPosition = new CameraPosition(new LatLng(lat,lon), zoom, position.tilt, position.bearing);
CameraUpdate update = CameraUpdateFactory.newCameraPosition(newPosition);
mMap.moveCamera(update);
}
/* Recursively call handler every 100ms */
sendEmptyMessageDelayed(0,100);
}
}
This handler must be defined as a field inside current class (I did this in a class that extends SupportMapFragment)
private OverscrollHandler mOverscrollHandler = new OverscrollHandler();
And lastly it must be called for the first time, i did it at the end of onActivityCreated to be sure that map exists.
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mContext = getActivity();
mMap = getMap();
mMap.setMapType(GoogleMap.MAP_TYPE_NONE);
mMap.addTileOverlay(new TileOverlayOptions().tileProvider(new VexLocalTileProvider(getResources().getAssets())));
CameraUpdate upd = CameraUpdateFactory.newLatLngZoom(new LatLng(41.87145, 12.52849), 14);
mMap.moveCamera(upd);
mOverscrollHandler.sendEmptyMessageDelayed(0,100);
}
Hope you will find it useful!
Instead of using new and shiny push technology which is onCameraChange, you may try to use old poll technology: map.getCameraPosition() or map.getProjection().getVisibleRegion(). You can then check if returned value is what you like and if not, map.moveCamera(...).
Basically you need a Handler, which will get the value of camera position in handleMessage and you send messages to this handler every 10ms or so. Inside handleMessage do sendEmptyMessageDelayed.
You can also use Runnable instead of Message (but that's a matter of taste).
limit zoom you can user this code
private GoogleMap mMap;
// Set a preference for minimum and maximum zoom.
mMap.setMinZoomPreference(6.0f);
mMap.setMaxZoomPreference(14.0f);
In maps API v2 there's a Min/MaxZoomLevel on the GoogleMap class, but I don't know if you can set it in any way.
Another approach would be to add a
GoogleMap.OnCameraChangeListener
to your map and by implementing
public void onCameraChange(CameraPosition cameraPosition);
To restrict the visible area, using GoogleMap.moveCamera(cameraPosition)
That is if you wish the user to be able to scroll or zoom "some".
You can also totally deactivate the scroll/zoom events via GoogleMapOptions
this may be dumb question but i couldn't find any solution with previous answer regarding this question now let me post my question i need to make ripple effect around circle image in google map marker , now let me post what i have tried so far:
#Override
public void onLocationChanged(final Location location) {
mLastLocation=location;
GradientDrawable d = new GradientDrawable();
d.setShape(GradientDrawable.OVAL);
d.setSize(500,500);
d.setColor(0x555751FF);
d.setStroke(5, Color.TRANSPARENT);
Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth()
, d.getIntrinsicHeight()
, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
d.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
d.draw(canvas);
final int radius = 100;
final GroundOverlay circle = mMap.addGroundOverlay(new GroundOverlayOptions()
.position(new LatLng(location.getLatitude(),location.getLongitude()), 2 * radius).image(BitmapDescriptorFactory.fromBitmap(createMarker(TrackingActivity.this,new LatLng(location.getLatitude(),location.getLongitude())))));
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
valueAnimator.setIntValues(0, radius);
valueAnimator.setDuration(3000);
valueAnimator.setEvaluator(new IntEvaluator());
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float animatedFraction = valueAnimator.getAnimatedFraction();
circle.setTransparency(animatedFraction);
// circle.setDimensions(animatedFraction * radius * 2);
}
});
valueAnimator.start();
// mMap.addMarker(new MarkerOptions().position(new LatLng(location.getLatitude(),location.getLongitude())).icon(BitmapDescriptorFactory.fromBitmap(createMarker(this,new LatLng(location.getLatitude(),location.getLongitude())))));
closeDialog();
public Bitmap createMarker(Context context, LatLng point) {
int px = context.getResources().getDimensionPixelSize(R.dimen.map_marker_diameter);
View markerView = ((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.circle_imgview, null);
markerView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
markerView.layout(0, 0, px, px);
markerView.buildDrawingCache();
CircleImageView bedNumberTextView = markerView.findViewById(R.id.circleimg);
Bitmap mDotMarkerBitmap = Bitmap.createBitmap(px, px, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(mDotMarkerBitmap);
Picasso.with(this).load("https://fww").into(bedNumberTextView);
markerView.draw(canvas);
return mDotMarkerBitmap;
}
}
The above code make my entire circle image ripple , but i only want to make ripple around my circle image how can i achieve this. Even i have tried using third party ripple animation and wrap my image inside that animation but it is not working to how can i achieve this friends. Thanks in advance!!
Try this Code. it's very easy to implement.
I hope this code help for you.
MapRipple.class
public class MapRipple {
private GoogleMap mGoogleMap;
private LatLng mLatLng, mPrevLatLng;
private BitmapDescriptor mBackgroundImageDescriptor;
private float mTransparency = 0.5f;
private volatile double mDistance = 100;
private int mNumberOfRipples = 1;
private int mFillColor = Color.TRANSPARENT;
private int mStrokeColor = Color.BLACK;
private int mStrokeWidth = 5;
private long mDurationBetweenTwoRipples = 1000;
private long mRippleDuration = 2000;
private ValueAnimator mAnimators[];
private Handler mHandlers[];
private GroundOverlay mGroundOverlays[];
private GradientDrawable mBackground;
private boolean isAnimationRunning = false;
public MapRipple(GoogleMap googleMap, LatLng latLng, Context context) {
mGoogleMap = googleMap;
mLatLng = latLng;
mPrevLatLng = latLng;
mBackground = (GradientDrawable) ContextCompat.getDrawable(context, R.drawable.map_background);
mAnimators = new ValueAnimator[4];
mHandlers = new Handler[4];
mGroundOverlays = new GroundOverlay[4];
}
public MapRipple withTransparency(float transparency) {
mTransparency = transparency;
return this;
}
public MapRipple withDistance(double distance) {
mDistance = distance;
return this;
}
public MapRipple withLatLng(LatLng latLng) {
mPrevLatLng = mLatLng;
mLatLng = latLng;
return this;
}
public MapRipple withNumberOfRipples(int numberOfRipples) {
if (numberOfRipples > 4 || numberOfRipples < 1) {
numberOfRipples = 4;
}
mNumberOfRipples = numberOfRipples;
return this;
}
public MapRipple withFillColor(int fillColor) {
mFillColor = fillColor;
return this;
}
public MapRipple withStrokeColor(int strokeColor) {
mStrokeColor = strokeColor;
return this;
}
#Deprecated
public void withStrokewidth(int strokeWidth) {
mStrokeWidth = strokeWidth;
}
public MapRipple withStrokeWidth(int strokeWidth) {
mStrokeWidth = strokeWidth;
return this;
}
public MapRipple withDurationBetweenTwoRipples(long durationBetweenTwoRipples) {
mDurationBetweenTwoRipples = durationBetweenTwoRipples;
return this;
}
public boolean isAnimationRunning() {
return isAnimationRunning;
}
public MapRipple withRippleDuration(long rippleDuration) {
mRippleDuration = rippleDuration;
return this;
}
private final Runnable mCircleOneRunnable = new Runnable() {
#Override
public void run() {
mGroundOverlays[0] = mGoogleMap.addGroundOverlay(new GroundOverlayOptions()
.position(mLatLng, (int) mDistance)
.transparency(mTransparency)
.image(mBackgroundImageDescriptor));
startAnimation(0);
}
};
private final Runnable mCircleTwoRunnable = new Runnable() {
#Override
public void run() {
mGroundOverlays[1] = mGoogleMap.addGroundOverlay(new GroundOverlayOptions()
.position(mLatLng, (int) mDistance)
.transparency(mTransparency)
.image(mBackgroundImageDescriptor));
startAnimation(1);
}
};
private final Runnable mCircleThreeRunnable = new Runnable() {
#Override
public void run() {
mGroundOverlays[2] = mGoogleMap.addGroundOverlay(new GroundOverlayOptions()
.position(mLatLng, (int) mDistance)
.transparency(mTransparency)
.image(mBackgroundImageDescriptor));
startAnimation(2);
}
};
private final Runnable mCircleFourRunnable = new Runnable() {
#Override
public void run() {
mGroundOverlays[3] = mGoogleMap.addGroundOverlay(new GroundOverlayOptions()
.position(mLatLng, (int) mDistance)
.transparency(mTransparency)
.image(mBackgroundImageDescriptor));
startAnimation(3);
}
};
private void startAnimation(final int numberOfRipple) {
ValueAnimator animator = ValueAnimator.ofInt(0, (int) mDistance);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.setDuration(mRippleDuration);
animator.setEvaluator(new IntEvaluator());
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int animated = (int) valueAnimator.getAnimatedValue();
mGroundOverlays[numberOfRipple].setDimensions(animated);
if (mDistance - animated <= 10) {
if (mLatLng != mPrevLatLng) {
mGroundOverlays[numberOfRipple].setPosition(mLatLng);
}
}
}
});
animator.start();
mAnimators[numberOfRipple] = animator;
}
private void setDrawableAndBitmap() {
mBackground.setColor(mFillColor);
mBackground.setStroke(UiUtil.dpToPx(mStrokeWidth), mStrokeColor);
mBackgroundImageDescriptor = UiUtil.drawableToBitmapDescriptor(mBackground);
}
public void stopRippleMapAnimation() {
if (isAnimationRunning) {
try {
for (int i = 0; i < mNumberOfRipples; i++) {
if (i == 0) {
mHandlers[i].removeCallbacks(mCircleOneRunnable);
mAnimators[i].cancel();
mGroundOverlays[i].remove();
}
if (i == 1) {
mHandlers[i].removeCallbacks(mCircleTwoRunnable);
mAnimators[i].cancel();
mGroundOverlays[i].remove();
}
if (i == 2) {
mHandlers[i].removeCallbacks(mCircleThreeRunnable);
mAnimators[i].cancel();
mGroundOverlays[i].remove();
}
if (i == 3) {
mHandlers[i].removeCallbacks(mCircleFourRunnable);
mAnimators[i].cancel();
mGroundOverlays[i].remove();
}
}
} catch (Exception e) {
//no need to handle it
}
}
isAnimationRunning = false;
}
public void startRippleMapAnimation() {
if (!isAnimationRunning) {
setDrawableAndBitmap();
for (int i = 0; i < mNumberOfRipples; i++) {
if (i == 0) {
mHandlers[i] = new Handler();
mHandlers[i].postDelayed(mCircleOneRunnable, mDurationBetweenTwoRipples * i);
}
if (i == 1) {
mHandlers[i] = new Handler();
mHandlers[i].postDelayed(mCircleTwoRunnable, mDurationBetweenTwoRipples * i);
}
if (i == 2) {
mHandlers[i] = new Handler();
mHandlers[i].postDelayed(mCircleThreeRunnable, mDurationBetweenTwoRipples * i);
}
if (i == 3) {
mHandlers[i] = new Handler();
mHandlers[i].postDelayed(mCircleFourRunnable, mDurationBetweenTwoRipples * i);
}
}
}
isAnimationRunning = true;
}
}
R.drawable.map_background
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:width="150dp"
android:height="150dp" />
<stroke
android:width="0.5dp"
android:color="#000000" />
</shape>
PolyUtil.class
public class PolyUtil {
private PolyUtil() {}
private static double tanLatGC(double lat1, double lat2, double lng2, double lng3) {
return (tan(lat1) * sin(lng2 - lng3) + tan(lat2) * sin(lng3)) / sin(lng2);
}
private static double mercatorLatRhumb(double lat1, double lat2, double lng2, double lng3) {
return (mercator(lat1) * (lng2 - lng3) + mercator(lat2) * lng3) / lng2;
}
private static boolean intersects(double lat1, double lat2, double lng2,
double lat3, double lng3, boolean geodesic) {
// Both ends on the same side of lng3.
if ((lng3 >= 0 && lng3 >= lng2) || (lng3 < 0 && lng3 < lng2)) {
return false;
}
// Point is South Pole.
if (lat3 <= -PI/2) {
return false;
}
// Any segment end is a pole.
if (lat1 <= -PI/2 || lat2 <= -PI/2 || lat1 >= PI/2 || lat2 >= PI/2) {
return false;
}
if (lng2 <= -PI) {
return false;
}
double linearLat = (lat1 * (lng2 - lng3) + lat2 * lng3) / lng2;
// Northern hemisphere and point under lat-lng line.
if (lat1 >= 0 && lat2 >= 0 && lat3 < linearLat) {
return false;
}
// Southern hemisphere and point above lat-lng line.
if (lat1 <= 0 && lat2 <= 0 && lat3 >= linearLat) {
return true;
}
// North Pole.
if (lat3 >= PI/2) {
return true;
}
return geodesic ?
tan(lat3) >= tanLatGC(lat1, lat2, lng2, lng3) :
mercator(lat3) >= mercatorLatRhumb(lat1, lat2, lng2, lng3);
}
public static boolean containsLocation(LatLng point, List<LatLng> polygon, boolean geodesic) {
return containsLocation(point.latitude, point.longitude, polygon, geodesic);
}
public static boolean containsLocation(double latitude, double longitude, List<LatLng> polygon, boolean geodesic) {
final int size = polygon.size();
if (size == 0) {
return false;
}
double lat3 = toRadians(latitude);
double lng3 = toRadians(longitude);
LatLng prev = polygon.get(size - 1);
double lat1 = toRadians(prev.latitude);
double lng1 = toRadians(prev.longitude);
int nIntersect = 0;
for (LatLng point2 : polygon) {
double dLng3 = wrap(lng3 - lng1, -PI, PI);
// Special case: point equal to vertex is inside.
if (lat3 == lat1 && dLng3 == 0) {
return true;
}
double lat2 = toRadians(point2.latitude);
double lng2 = toRadians(point2.longitude);
// Offset longitudes by -lng1.
if (intersects(lat1, lat2, wrap(lng2 - lng1, -PI, PI), lat3, dLng3, geodesic)) {
++nIntersect;
}
lat1 = lat2;
lng1 = lng2;
}
return (nIntersect & 1) != 0;
}
private static final double DEFAULT_TOLERANCE = 0.1; // meters.
public static boolean isLocationOnEdge(LatLng point, List<LatLng> polygon, boolean geodesic,
double tolerance) {
return isLocationOnEdgeOrPath(point, polygon, true, geodesic, tolerance);
}
public static boolean isLocationOnEdge(LatLng point, List<LatLng> polygon, boolean geodesic) {
return isLocationOnEdge(point, polygon, geodesic, DEFAULT_TOLERANCE);
}
public static boolean isLocationOnPath(LatLng point, List<LatLng> polyline,
boolean geodesic, double tolerance) {
return isLocationOnEdgeOrPath(point, polyline, false, geodesic, tolerance);
}
public static boolean isLocationOnPath(LatLng point, List<LatLng> polyline,
boolean geodesic) {
return isLocationOnPath(point, polyline, geodesic, DEFAULT_TOLERANCE);
}
private static boolean isLocationOnEdgeOrPath(LatLng point, List<LatLng> poly, boolean closed,
boolean geodesic, double toleranceEarth) {
int idx = locationIndexOnEdgeOrPath(point, poly, closed, geodesic, toleranceEarth);
return (idx >= 0);
}
public static int locationIndexOnPath(LatLng point, List<LatLng> poly,
boolean geodesic, double tolerance) {
return locationIndexOnEdgeOrPath(point, poly, false, geodesic, tolerance);
}
public static int locationIndexOnPath(LatLng point, List<LatLng> polyline,
boolean geodesic) {
return locationIndexOnPath(point, polyline, geodesic, DEFAULT_TOLERANCE);
}
private static int locationIndexOnEdgeOrPath(LatLng point, List<LatLng> poly, boolean closed,
boolean geodesic, double toleranceEarth) {
int size = poly.size();
if (size == 0) {
return -1;
}
double tolerance = toleranceEarth / EARTH_RADIUS;
double havTolerance = hav(tolerance);
double lat3 = toRadians(point.latitude);
double lng3 = toRadians(point.longitude);
LatLng prev = poly.get(closed ? size - 1 : 0);
double lat1 = toRadians(prev.latitude);
double lng1 = toRadians(prev.longitude);
int idx = 0;
if (geodesic) {
for (LatLng point2 : poly) {
double lat2 = toRadians(point2.latitude);
double lng2 = toRadians(point2.longitude);
if (isOnSegmentGC(lat1, lng1, lat2, lng2, lat3, lng3, havTolerance)) {
return Math.max(0, idx - 1);
}
lat1 = lat2;
lng1 = lng2;
idx++;
}
} else {
double minAcceptable = lat3 - tolerance;
double maxAcceptable = lat3 + tolerance;
double y1 = mercator(lat1);
double y3 = mercator(lat3);
double[] xTry = new double[3];
for (LatLng point2 : poly) {
double lat2 = toRadians(point2.latitude);
double y2 = mercator(lat2);
double lng2 = toRadians(point2.longitude);
if (max(lat1, lat2) >= minAcceptable && min(lat1, lat2) <= maxAcceptable) {
// We offset longitudes by -lng1; the implicit x1 is 0.
double x2 = wrap(lng2 - lng1, -PI, PI);
double x3Base = wrap(lng3 - lng1, -PI, PI);
xTry[0] = x3Base;
// Also explore wrapping of x3Base around the world in both directions.
xTry[1] = x3Base + 2 * PI;
xTry[2] = x3Base - 2 * PI;
for (double x3 : xTry) {
double dy = y2 - y1;
double len2 = x2 * x2 + dy * dy;
double t = len2 <= 0 ? 0 : clamp((x3 * x2 + (y3 - y1) * dy) / len2, 0, 1);
double xClosest = t * x2;
double yClosest = y1 + t * dy;
double latClosest = inverseMercator(yClosest);
double havDist = havDistance(lat3, latClosest, x3 - xClosest);
if (havDist < havTolerance) {
return Math.max(0, idx - 1);
}
}
}
lat1 = lat2;
lng1 = lng2;
y1 = y2;
idx++;
}
}
return -1;
}
private static double sinDeltaBearing(double lat1, double lng1, double lat2, double lng2,
double lat3, double lng3) {
double sinLat1 = sin(lat1);
double cosLat2 = cos(lat2);
double cosLat3 = cos(lat3);
double lat31 = lat3 - lat1;
double lng31 = lng3 - lng1;
double lat21 = lat2 - lat1;
double lng21 = lng2 - lng1;
double a = sin(lng31) * cosLat3;
double c = sin(lng21) * cosLat2;
double b = sin(lat31) + 2 * sinLat1 * cosLat3 * hav(lng31);
double d = sin(lat21) + 2 * sinLat1 * cosLat2 * hav(lng21);
double denom = (a * a + b * b) * (c * c + d * d);
return denom <= 0 ? 1 : (a * d - b * c) / sqrt(denom);
}
private static boolean isOnSegmentGC(double lat1, double lng1, double lat2, double lng2,
double lat3, double lng3, double havTolerance) {
double havDist13 = havDistance(lat1, lat3, lng1 - lng3);
if (havDist13 <= havTolerance) {
return true;
}
double havDist23 = havDistance(lat2, lat3, lng2 - lng3);
if (havDist23 <= havTolerance) {
return true;
}
double sinBearing = sinDeltaBearing(lat1, lng1, lat2, lng2, lat3, lng3);
double sinDist13 = sinFromHav(havDist13);
double havCrossTrack = havFromSin(sinDist13 * sinBearing);
if (havCrossTrack > havTolerance) {
return false;
}
double havDist12 = havDistance(lat1, lat2, lng1 - lng2);
double term = havDist12 + havCrossTrack * (1 - 2 * havDist12);
if (havDist13 > term || havDist23 > term) {
return false;
}
if (havDist12 < 0.74) {
return true;
}
double cosCrossTrack = 1 - 2 * havCrossTrack;
double havAlongTrack13 = (havDist13 - havCrossTrack) / cosCrossTrack;
double havAlongTrack23 = (havDist23 - havCrossTrack) / cosCrossTrack;
double sinSumAlongTrack = sinSumFromHav(havAlongTrack13, havAlongTrack23);
return sinSumAlongTrack > 0; // Compare with half-circle == PI using sign of sin().
}
public static List<LatLng> simplify(List<LatLng> poly, double tolerance) {
final int n = poly.size();
if (n < 1) {
throw new IllegalArgumentException("Polyline must have at least 1 point");
}
if (tolerance <= 0) {
throw new IllegalArgumentException("Tolerance must be greater than zero");
}
boolean closedPolygon = isClosedPolygon(poly);
LatLng lastPoint = null;
if (closedPolygon) {
final double OFFSET = 0.00000000001;
lastPoint = poly.get(poly.size() - 1);
poly.remove(poly.size() - 1);
poly.add(new LatLng(lastPoint.latitude + OFFSET, lastPoint.longitude + OFFSET));
}
int idx;
int maxIdx = 0;
Stack<int[]> stack = new Stack<>();
double[] dists = new double[n];
dists[0] = 1;
dists[n - 1] = 1;
double maxDist;
double dist = 0.0;
int[] current;
if (n > 2) {
int[] stackVal = new int[]{0, (n - 1)};
stack.push(stackVal);
while (stack.size() > 0) {
current = stack.pop();
maxDist = 0;
for (idx = current[0] + 1; idx < current[1]; ++idx) {
dist = distanceToLine(poly.get(idx), poly.get(current[0]),
poly.get(current[1]));
if (dist > maxDist) {
maxDist = dist;
maxIdx = idx;
}
}
if (maxDist > tolerance) {
dists[maxIdx] = maxDist;
int[] stackValCurMax = {current[0], maxIdx};
stack.push(stackValCurMax);
int[] stackValMaxCur = {maxIdx, current[1]};
stack.push(stackValMaxCur);
}
}
}
if (closedPolygon) {
poly.remove(poly.size() - 1);
poly.add(lastPoint);
}
idx = 0;
ArrayList<LatLng> simplifiedLine = new ArrayList<>();
for (LatLng l : poly) {
if (dists[idx] != 0) {
simplifiedLine.add(l);
}
idx++;
}
return simplifiedLine;
}
public static boolean isClosedPolygon(List<LatLng> poly) {
LatLng firstPoint = poly.get(0);
LatLng lastPoint = poly.get(poly.size()-1);
return firstPoint.equals(lastPoint);
}
public static double distanceToLine(final LatLng p, final LatLng start, final LatLng end) {
if (start.equals(end)) {
return computeDistanceBetween(end, p);
}
final double s0lat = toRadians(p.latitude);
final double s0lng = toRadians(p.longitude);
final double s1lat = toRadians(start.latitude);
final double s1lng = toRadians(start.longitude);
final double s2lat = toRadians(end.latitude);
final double s2lng = toRadians(end.longitude);
double s2s1lat = s2lat - s1lat;
double s2s1lng = s2lng - s1lng;
final double u = ((s0lat - s1lat) * s2s1lat + (s0lng - s1lng) * s2s1lng)
/ (s2s1lat * s2s1lat + s2s1lng * s2s1lng);
if (u <= 0) {
return computeDistanceBetween(p, start);
}
if (u >= 1) {
return computeDistanceBetween(p, end);
}
LatLng sa = new LatLng(p.latitude - start.latitude, p.longitude - start.longitude);
LatLng sb = new LatLng(u * (end.latitude - start.latitude), u * (end.longitude - start.longitude));
return computeDistanceBetween(sa, sb);
}
public static List<LatLng> decode(final String encodedPath) {
int len = encodedPath.length();
final List<LatLng> path = new ArrayList<LatLng>();
int index = 0;
int lat = 0;
int lng = 0;
while (index < len) {
int result = 1;
int shift = 0;
int b;
do {
b = encodedPath.charAt(index++) - 63 - 1;
result += b << shift;
shift += 5;
} while (b >= 0x1f);
lat += (result & 1) != 0 ? ~(result >> 1) : (result >> 1);
result = 1;
shift = 0;
do {
b = encodedPath.charAt(index++) - 63 - 1;
result += b << shift;
shift += 5;
} while (b >= 0x1f);
lng += (result & 1) != 0 ? ~(result >> 1) : (result >> 1);
path.add(new LatLng(lat * 1e-5, lng * 1e-5));
}
return path;
}
public static String encode(final List<LatLng> path) {
long lastLat = 0;
long lastLng = 0;
final StringBuffer result = new StringBuffer();
for (final LatLng point : path) {
long lat = Math.round(point.latitude * 1e5);
long lng = Math.round(point.longitude * 1e5);
long dLat = lat - lastLat;
long dLng = lng - lastLng;
encode(dLat, result);
encode(dLng, result);
lastLat = lat;
lastLng = lng;
}
return result.toString();
}
private static void encode(long v, StringBuffer result) {
v = v < 0 ? ~(v << 1) : v << 1;
while (v >= 0x20) {
result.append(Character.toChars((int) ((0x20 | (v & 0x1f)) + 63)));
v >>= 5;
}
result.append(Character.toChars((int) (v + 63)));
}
}
Google Map Activity
MapRipple mapRipple = new MapRipple(mGoogleMap, new LatLng(gpsTracker.getLatitude(), gpsTracker.getLongitude()), this);
mapRipple.stopRippleMapAnimation();
mapRipple.withNumberOfRipples(3);
mapRipple.withFillColor(Color.parseColor("#FFA3D2E4"));
mapRipple.withStrokeColor(Color.BLACK);
mapRipple.withStrokewidth(0);
mapRipple.withDistance(2000);
mapRipple.withRippleDuration(12000);
mapRipple.withDurationBetweenTwoRipples(1000);
mapRipple.withTransparency(0.5f);
mapRipple.startRippleMapAnimation();
Custom Marker pin
mGoogleMap.addMarker(new MarkerOptions()
.icon(BitmapDescriptorFactory.fromBitmap(YourMarkerPinBitmap))
.anchor(0.5f, 0.6f)
.position(new LatLng(gpsTracker.getLatitude(), gpsTracker.getLongitude())));
Location Change
if (mapRipple != null && mapRipple.isAnimationRunning())
mapRipple.withLatLng(new LatLng(lati, longi));
I am working on a project in which I am showing two random location and the path between them.I have used this tutorial to accomplish.
Now i want to show the moving image from one location to another.I have already put markers on that two locations.and also I have saved the position in an arraylist.
I had found some similar posts but couldn't solve my issue.
Here is my code for moving the drawable:
mMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
final Handler handler = new Handler();
int i = 0;
#Override
public boolean onMarkerClick(Marker marker) {
// System.out.println("Marker size:- " + MarkerPoints.size());
handler.post(new Runnable() {
#Override
public void run() {
BitmapDescriptor icon = BitmapDescriptorFactory.fromResource(R.drawable.truck_16);
while (i < MarkerPoints.size()) {
MarkerOptions markerOptions = new MarkerOptions().position(MarkerPoints.get(i))
.title("Current Location")
icon(icon);
System.out.println(MarkerPoints.get(i));
mMap.addMarker(markerOptions);
i++;
handler.postDelayed(this, 3000);
}
}
});
return true;
}
});
private double bearingBetweenLocations(LatLng latLng1, LatLng latLng2) {
final double PI = 3.14159;
final double lat1 = latLng1.latitude * PI / 180;
final double long1 = latLng1.longitude * PI / 180;
final double lat2 = latLng2.latitude * PI / 180;
final double long2 = latLng2.longitude * PI / 180;
final double dLon = (long2 - long1);
final double y = Math.sin(dLon) * Math.cos(lat2);
final double x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1)
* Math.cos(lat2) * Math.cos(dLon);
double brng = Math.atan2(y, x);
brng = Math.toDegrees(brng);
brng = (brng + 360) % 360;
return brng;
}
Suppose you have current and destination co-ordinates like below.
private LatLng CURRENT_LOC = new LatLng(23.013171, 72.522300);
private LatLng DESTINATION_LOC = new LatLng(23.013481, 72.522496);
After that add marker on google map
if (googleMap != null)
{
BitmapDescriptor icon = BitmapDescriptorFactory.fromResource(R.drawable.truck_16);
MarkerOptions current = new MarkerOptions().position(CURRENT_LOC).title("Current Point");
current_marker = googleMap.addMarker(current);
current_marker.setIcon(icon);
current_marker.setFlat(true);
MarkerOptions destination = new MarkerOptions().position(DESTINATION_LOC).title("Destination Point");
destination_marker = googleMap.addMarker(destination);
destination_marker.setFlat(true);
}
And move marker on click
#Override
public void onClick(View v)
{
switch (v.getId())
{
case R.id.btn_move:
float rotate = (float) bearingBetweenLocations(CURRENT_LOC, DESTINATION_LOC);
rotateMarker(rotate);
break;
}
}
below all are methods that move marker from current location to destnation
private double bearingBetweenLocations(LatLng latLng1,LatLng latLng2)
{
double PI = 3.14159;
double lat1 = latLng1.latitude * PI / 180;
double long1 = latLng1.longitude * PI / 180;
double lat2 = latLng2.latitude * PI / 180;
double long2 = latLng2.longitude * PI / 180;
double dLon = (long2 - long1);
double y = Math.sin(dLon) * Math.cos(lat2);
double x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1)
* Math.cos(lat2) * Math.cos(dLon);
double brng = Math.atan2(y, x);
brng = Math.toDegrees(brng);
brng = (brng + 360) % 360;
return brng;
}
public void rotateMarker(float rotate)
{
if (current_marker != null)
{
//final LatLngInterpolator latLngInterpolator = new LatLngInterpolator.LinearFixed();
ValueAnimator valueAnimator = new ValueAnimator();
//final LatLng startPosition = current_marker.getPosition();
final float startRotation = current_marker.getRotation();
final float angle = 180 - Math.abs(Math.abs(startRotation - rotate) - 180);
final float right = WhichWayToTurn(startRotation, rotate);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
{
#Override
public void onAnimationUpdate(ValueAnimator animation)
{
try
{
if (current_marker == null) // oops... destroying map during animation...
{
return;
}
float v = animation.getAnimatedFraction();
//newPosition = latLngInterpolator.interpolate(v, startPosition, toLatLng(location));
float rotation = startRotation + right * v * angle;
current_marker.setRotation(rotation);
//current_marker.setPosition(newPosition);
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
});
valueAnimator.addListener(new AnimatorListenerAdapter()
{
#Override
public void onAnimationEnd(Animator animation)
{
//current_marker.setPosition(newPosition);
animateMarker(current_marker, DESTINATION_LOC, false);
}
});
valueAnimator.setFloatValues(0, 1);
valueAnimator.setDuration(1000);
valueAnimator.start();
}
}
public void animateMarker(final Marker marker, final LatLng toPosition, final boolean hideMarker)
{
final Handler handler = new Handler();
final long start = SystemClock.uptimeMillis();
Projection proj = googleMap.getProjection();
Point startPoint = proj.toScreenLocation(marker.getPosition());
final LatLng startLatLng = proj.fromScreenLocation(startPoint);
final long duration = 5000;
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 * toPosition.longitude + (1 - t) * startLatLng.longitude;
double lat = t * toPosition.latitude + (1 - t) * startLatLng.latitude;
marker.setPosition(new LatLng(lat, lng));
if (t < 1.0) {
// Post again 16ms later.
handler.postDelayed(this, 16);
} else {
if (hideMarker) {
marker.setVisible(false);
} else {
marker.setVisible(true);
}
}
}
});
}
private float WhichWayToTurn(float currentDirection, float targetDirection)
{
float diff = targetDirection - currentDirection;
if (Math.abs(diff) == 0)
{
return 0;
}
if(diff > 180)
{
return -1;
}
else
{
return 1;
}
}
Hope this would help you.
Finally I found a way to accomplish this.May be this is not the best way but it solves the problem.I am posting this so in future if someone needs to do stuff like that,It can be helpful.
I have store all the points in an ArrayList called MarkerPoints that we get from the API and use it to draw the image on ever point.
Here is the code:
public class MyActivity extends FragmentActivity implements Runnable
{
private Thread thread = null;
volatile boolean isRunning;
private ArrayList<LatLng> MarkerPoints; //This arrayList contains all points that are on the route
int i = 0;
Marker marker;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_on_trip_maps);
isRunning = true;
}
#Override
public void run() {
while (isRunning) {
runOnUiThread(new Runnable() {
#Override
public void run() {
update();
}
});
control();
}
}
public void update() {
if (marker != null) {
marker.remove();
}
BitmapDescriptor icon = BitmapDescriptorFactory.fromResource(R.drawable.truck_16);
marker = mMap.addMarker(new MarkerOptions().position(MarkerPoints.get(i))
.title("Current Location")
.icon(icon));
System.out.println(MarkerPoints.get(i));
i++;
if (i > MarkerPoints.size() - 1) {
isRunning = false;
}
}
public void control() {
try {
thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
This will give the moving effect.
I would like to calculate the are of a polygon drawn in a map fragment for a college project.
This is how I draw my polygon.
#Override
public void onMapClick(LatLng point) {
//tvLocInfo.setText("New marker added#" + point.toString());
map.addMarker(new MarkerOptions().position(point).draggable(true).title(point.toString()));
markerClicked = false;
}
#Override
public void onMapLongClick(LatLng point) {
//tvLocInfo.setText("New marker added#" + point.toString());
map.clear();
}
#Override
public boolean onMarkerClick(Marker marker) {
if(markerClicked){
if(polygon != null){
polygon.remove();
polygon = null;
}
polygonOptions.add(marker.getPosition());
polygonOptions.strokeColor(Color.RED);
polygonOptions.fillColor(Color.BLUE);
polygon = map.addPolygon(polygonOptions);
//Area = google.maps.geometry.spherical.computeArea(polygon.getPath().getArray());
}else{
if(polygon != null){
polygon.remove();
polygon = null;
}
polygonOptions = new PolygonOptions().add(marker.getPosition());
markerClicked = true;
}
I have seen this code on how to calculate the area but I am unsure how to implement it in my application and calculate the area of my polygon.
I use this code to calculate an area of a GPS with Android:
private static final double EARTH_RADIUS = 6371000;// meters
public static double calculateAreaOfGPSPolygonOnEarthInSquareMeters(final List<Location> locations) {
return calculateAreaOfGPSPolygonOnSphereInSquareMeters(locations, EARTH_RADIUS);
}
private static double calculateAreaOfGPSPolygonOnSphereInSquareMeters(final List<Location> locations, final double radius) {
if (locations.size() < 3) {
return 0;
}
final double diameter = radius * 2;
final double circumference = diameter * Math.PI;
final List<Double> listY = new ArrayList<Double>();
final List<Double> listX = new ArrayList<Double>();
final List<Double> listArea = new ArrayList<Double>();
// calculate segment x and y in degrees for each point
final double latitudeRef = locations.get(0).getLatitude();
final double longitudeRef = locations.get(0).getLongitude();
for (int i = 1; i < locations.size(); i++) {
final double latitude = locations.get(i).getLatitude();
final double longitude = locations.get(i).getLongitude();
listY.add(calculateYSegment(latitudeRef, latitude, circumference));
Log.d(LOG_TAG, String.format("Y %s: %s", listY.size() - 1, listY.get(listY.size() - 1)));
listX.add(calculateXSegment(longitudeRef, longitude, latitude, circumference));
Log.d(LOG_TAG, String.format("X %s: %s", listX.size() - 1, listX.get(listX.size() - 1)));
}
// calculate areas for each triangle segment
for (int i = 1; i < listX.size(); i++) {
final double x1 = listX.get(i - 1);
final double y1 = listY.get(i - 1);
final double x2 = listX.get(i);
final double y2 = listY.get(i);
listArea.add(calculateAreaInSquareMeters(x1, x2, y1, y2));
Log.d(LOG_TAG, String.format("area %s: %s", listArea.size() - 1, listArea.get(listArea.size() - 1)));
}
// sum areas of all triangle segments
double areasSum = 0;
for (final Double area : listArea) {
areasSum = areasSum + area;
}
// get abolute value of area, it can't be negative
return Math.abs(areasSum);// Math.sqrt(areasSum * areasSum);
}
private static Double calculateAreaInSquareMeters(final double x1, final double x2, final double y1, final double y2) {
return (y1 * x2 - x1 * y2) / 2;
}
private static double calculateYSegment(final double latitudeRef, final double latitude, final double circumference) {
return (latitude - latitudeRef) * circumference / 360.0;
}
private static double calculateXSegment(final double longitudeRef, final double longitude, final double latitude,
final double circumference) {
return (longitude - longitudeRef) * circumference * Math.cos(Math.toRadians(latitude)) / 360.0;
}
I could also use the following polygon which is static if calculating the area of the drawn polygon is not possible.
Polygon UCCpolygon = map.addPolygon(new PolygonOptions()
.add(new LatLng(51.893728, -8.491865),
new LatLng(51.893550, -8.492479),
new LatLng(51.893216, -8.492224),
new LatLng(51.893404, -8.491598))
.strokeColor(Color.RED)
.fillColor(Color.BLUE));
Thanks for the help!
Sean
There's already a library for that.
import com.google.maps.android.SphericalUtil;
//...
List<LatLng> latLngs = new ArrayList<>();
latLngs.add(new LatLng(51.893728, -8.491865));
latLngs.add(new LatLng(51.893550, -8.492479));
latLngs.add(new LatLng(51.893216, -8.492224));
latLngs.add(new LatLng(51.893404, -8.491598));
Log.i(TAG, "computeArea " + SphericalUtil.computeArea(latLngs));
For me the output is computeArea 1920.8879882782069
If you want to use SphericalUtils code without any library, you can use following code. it's taken from opensource code from SphericalUtils.java and other class. I have taken this code and used it as i was using MapBox and MapBox does not have implemented the calculateArea function in Turf.
import java.util.List;
import pojo.LatLng;
import static java.lang.Math.PI;
import static java.lang.Math.abs;
import static java.lang.Math.atan2;
import static java.lang.Math.cos;
import static java.lang.Math.sin;
import static java.lang.Math.tan;
import static java.lang.Math.toRadians;
public class PolygonUtils {
/**
* The earth's radius, in meters.
* Mean radius as defined by IUGG.
*/
static final double EARTH_RADIUS = 6371009;
/**
* Returns the area of a closed path on Earth.
* #param path A closed path.
* #return The path's area in square meters.
*/
public static double computeArea(List<LatLng> path) {
return abs(computeSignedArea(path,EARTH_RADIUS));
}
/**
* Returns the signed area of a closed path on a sphere of given radius.
* The computed area uses the same units as the radius squared.
* Used by SphericalUtilTest.
*/
static double computeSignedArea(List<LatLng> path, double radius) {
int size = path.size();
if (size < 3) { return 0; }
double total = 0;
LatLng prev = path.get(size - 1);
double prevTanLat = tan((PI / 2 - toRadians(prev.getLatitude())) / 2);
double prevLng = toRadians(prev.getLongitude());
// For each edge, accumulate the signed area of the triangle formed by the North Pole
// and that edge ("polar triangle").
for (LatLng point : path) {
double tanLat = tan((PI / 2 - toRadians(point.getLatitude())) / 2);
double lng = toRadians(point.getLongitude());
total += polarTriangleArea(tanLat, lng, prevTanLat, prevLng);
prevTanLat = tanLat;
prevLng = lng;
}
return total * (radius * radius);
}
/**
* Returns the signed area of a triangle which has North Pole as a vertex.
* Formula derived from "Area of a spherical triangle given two edges and the included angle"
* as per "Spherical Trigonometry" by Todhunter, page 71, section 103, point 2.
* See http://books.google.com/books?id=3uBHAAAAIAAJ&pg=PA71
* The arguments named "tan" are tan((pi/2 - latitude)/2).
*/
private static double polarTriangleArea(double tan1, double lng1, double tan2, double lng2) {
double deltaLng = lng1 - lng2;
double t = tan1 * tan2;
return 2 * atan2(t * sin(deltaLng), 1 + t * cos(deltaLng));
}
}
I've added GroundOverlay to map and want to limit scrolling and zooming within this area.
How to limit scrolling within some bounds on android google maps?
Is it possible to get instantly motion points from MapFragment?
Please, help me.
Constraining the camera has (finally!) been added as a feature as part of the release of Google Play Services 9.4 -- you can call setLatLngBoundsForCameraTarget(LatLngBounds bounds) to set the allowed panning area.
// Create a LatLngBounds that includes the city of Adelaide in Australia.
final LatLngBounds ADELAIDE = new LatLngBounds(
new LatLng(-35.0, 138.58), new LatLng(-34.9, 138.61));
// Constrain the camera target to the Adelaide bounds.
mMap.setLatLngBoundsForCameraTarget(ADELAIDE);
You can find a thorough explanation in the documentation: Restricting the user's panning to a given area and a sample activity in GitHub.
May be it is too late, but here is my solution:
Disabled built-in GoogleMap's gestures.
Added custom gestures (for scrolling, fling and scaling).
Checking for allowed area when processing events.
Set bounds/zoom manually with standard Map's functions.
And here is my example:
[UPDATED]
There was a problem - when touch events were recieved before map was initialized.
check null values in onInterceptTouchEvent
Also I have discovered that my solution is slightly slowly than build-in function.
import android.content.Context;
import android.graphics.Point;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMapOptions;
import com.google.android.gms.maps.MapView;
import com.google.android.gms.maps.MapsInitializer;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.VisibleRegion;
public class RestrictedMapView extends MapView {
public static float MAX_ZOOM = 20;
public static float MIN_ZOOM = 5;
public static float MIN_ZOOM_FOR_FLING = 7;
public static double MAX_LONGITUDE = 183.61;
public static double MIN_LONGITUDE = 159.31;
public static double MAX_LATITUDE = -32.98;
public static double MIN_LATITUDE = -53.82;
public static double DEF_LATITUDE = -41.78;
public static double DEF_LONGITUDE = 173.02;
public static float DEF_ZOOM = 7;
private Handler mHandler = new Handler();
private Context mContext;
private VisibleRegion mLastCorrectRegion = null;
private boolean mIsInAnimation = false;
public RestrictedMapView(Context c, AttributeSet a, int o) {
super(c, a, o);
init(c);
}
public RestrictedMapView(Context c, AttributeSet a) {
super(c, a);
init(c);
}
public RestrictedMapView(Context c) {
super(c);
init(c);
}
public RestrictedMapView(Context c, GoogleMapOptions o) {
super(c, o);
init(c);
}
private GestureDetector mGestureDetector = null;
private GestureDetector.SimpleOnGestureListener mGestudeListener =
new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (mIsInAnimation) return false;
GoogleMap map = getMap();
LatLng target = map.getCameraPosition().target;
Point screenPoint = map.getProjection().toScreenLocation(target);
Point newPoint = new Point(screenPoint.x + (int)distanceX, screenPoint.y + (int)distanceY);
LatLng mapNewTarget = map.getProjection().fromScreenLocation(newPoint);
CameraUpdate update = CameraUpdateFactory.newLatLngZoom(
mapNewTarget,map.getCameraPosition().zoom);
tryUpdateCamera(update, 0);
return true;
}
#Override
public boolean onFling (MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (mIsInAnimation) return false;
GoogleMap map = getMap();
double zoom = map.getCameraPosition().zoom;
if (zoom < MIN_ZOOM_FOR_FLING)
return false;
int velocity = (int) Math.sqrt(velocityX * velocityX + velocityY * velocityY);
if (velocity < 500) return false;
double k1 = 0.002d; /*exipemental*/
double k2 = 0.002d;/*exipemental*/
LatLng target = map.getCameraPosition().target;
Point screenPoint = map.getProjection().toScreenLocation(target);
Point newPoint = new Point(screenPoint.x - (int)(velocityX * k1 * zoom * zoom/*exipemental*/),
screenPoint.y - (int)(velocityY * k1 * zoom * zoom/*exipemental*/));
LatLng mapNewTarget = map.getProjection().fromScreenLocation(newPoint);
CameraUpdate update = CameraUpdateFactory.newLatLngZoom(
mapNewTarget,map.getCameraPosition().zoom);
tryUpdateCamera(update, (int)(velocity * k2 * zoom * zoom) /*exipemental*/);
return true;
}
};
private ScaleGestureDetector mScaleGestureDetector = null;
private ScaleGestureDetector.SimpleOnScaleGestureListener mScaleGestudeListener =
new ScaleGestureDetector.SimpleOnScaleGestureListener() {
#Override
public boolean onScale (ScaleGestureDetector detector) {
if (mIsInAnimation) return false;
GoogleMap map = getMap();
double zoom = map.getCameraPosition().zoom;
double k = 1d / detector.getScaleFactor();
int x = (int) detector.getFocusX();
int y = (int) detector.getFocusY();
LatLng mapFocus = map.getProjection().
fromScreenLocation(new Point(x, y));
LatLng target = map.getCameraPosition().target;
zoom = zoom + Math.log(detector.getScaleFactor()) / Math.log(2d);
if (zoom < MIN_ZOOM)
if (zoom == MIN_ZOOM) return false;
else zoom = MIN_ZOOM;
if (zoom > MAX_ZOOM)
if (zoom == MAX_ZOOM) return false;
else zoom = MAX_ZOOM;
double dx = norm(mapFocus.longitude) - norm(target.longitude);
double dy = mapFocus.latitude - target.latitude;
double dk = 1d - 1d / k;
LatLng newTarget = new LatLng(target.latitude - dy * dk,
norm(target.longitude) - dx * dk);
CameraUpdate update = CameraUpdateFactory.newLatLngZoom(newTarget, (float) zoom);
tryUpdateCamera(update, 0);
return true;
}
};
private void tryUpdateCamera(CameraUpdate update, int animateTime) {
GoogleMap map = getMap();
final VisibleRegion reg = map.getProjection().getVisibleRegion();
if (animateTime <= 0) {
map.moveCamera(update);
checkCurrentRegion(reg);
} else {
mIsInAnimation = true;
map.animateCamera(update, animateTime, new GoogleMap.CancelableCallback() {
#Override
public void onFinish() {
mIsInAnimation = false;
checkCurrentRegion(reg);
}
#Override
public void onCancel() {
mIsInAnimation = false;
checkCurrentRegion(reg);
}
});
}
}
private void checkCurrentRegion(VisibleRegion oldReg) {
GoogleMap map = getMap();
VisibleRegion regNew = map.getProjection().getVisibleRegion();
if (checkBounds(regNew)) {
mLastCorrectRegion = regNew;
} else {
if (mLastCorrectRegion != null)
oldReg = mLastCorrectRegion;
mIsInAnimation = true;
map.animateCamera(CameraUpdateFactory.newLatLngBounds(
oldReg.latLngBounds, 0),
200, new GoogleMap.CancelableCallback() {
#Override
public void onFinish() {
mIsInAnimation = false;
}
#Override
public void onCancel() {
mIsInAnimation = false;
}
});
}
}
/**
*
*
* #param lonVal
* #return
*/
private double norm(double lonVal) {
while (lonVal > 360d) lonVal -= 360d;
while (lonVal < -360d) lonVal += 360d;
if (lonVal < 0) lonVal = 360d + lonVal;
return lonVal;
}
private double denorm(double lonVal) {
if (lonVal > 180d) lonVal = -360d + lonVal;
return lonVal;
}
private boolean checkBounds(VisibleRegion reg) {
double left = Math.min(
Math.min(norm(reg.farLeft.longitude), norm(reg.nearLeft.longitude)),
Math.min(norm(reg.farRight.longitude), norm(reg.nearRight.longitude)));
double right = Math.max(
Math.max(norm(reg.farLeft.longitude), norm(reg.nearLeft.longitude)),
Math.max(norm(reg.farRight.longitude), norm(reg.nearRight.longitude)));
double top = Math.max(
Math.max(reg.farLeft.latitude, reg.nearLeft.latitude),
Math.max(reg.farRight.latitude, reg.nearRight.latitude));
double bottom = Math.min(
Math.min(reg.farLeft.latitude, reg.nearLeft.latitude),
Math.min(reg.farRight.latitude, reg.nearRight.latitude));
boolean limitBounds = left < MIN_LONGITUDE || right > MAX_LONGITUDE ||
bottom < MIN_LATITUDE || top > MAX_LATITUDE;
return !limitBounds;
}
private void init(Context c) {
try {
MapsInitializer.initialize(c);
} catch (GooglePlayServicesNotAvailableException e) {
e.printStackTrace();
}
mContext = c;
mHandler.post(new Runnable() {
#Override
public void run() {
GoogleMap map = getMap();
if (map != null) {
getMap().getUiSettings().setZoomControlsEnabled(false);
map.getUiSettings().setAllGesturesEnabled(false);
map.moveCamera(CameraUpdateFactory.newLatLngZoom(
new LatLng(DEF_LATITUDE, DEF_LONGITUDE), DEF_ZOOM));
mLastCorrectRegion = map.getProjection().getVisibleRegion();
mGestureDetector = new GestureDetector(mContext, mGestudeListener);
mScaleGestureDetector = new ScaleGestureDetector(mContext, mScaleGestudeListener);
} else mHandler.post(this);
}
});
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mGestureDetector != null) mGestureDetector.onTouchEvent(event);
if (mScaleGestureDetector != null) mScaleGestureDetector.onTouchEvent(event);
return super.onInterceptTouchEvent(event);
}
}
Definition within my xml-layout of fragment:
<com.package....RestrictedMapView
android:id="#+id/mapview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
In xml file it is possible to define custom zoom/location buttons and set click listeners for manual manipulating camera (in this case you have to check MAX_ZOOM and MIN_ZOOM, and check if the current location is within the allowed bounds).
Too bad that google doesn't let us intercept and block the user, i found that MaciejGórski's answer is the one that suits best to my needs.
I wanted to share my solution (Based on his answer) with you.
First i defined the bounds and max/min zoom:
private final LatLngBounds BOUNDS = new LatLngBounds(new LatLng(41.8138,12.3891), new LatLng(41.9667, 12.5938));
private final int MAX_ZOOM = 18;
private final int MIN_ZOOM = 14;
Then i created this little function to test if current camera bounds are outside of max bounds and return the difference in latitude and longitude.
/**
* Returns the correction for Lat and Lng if camera is trying to get outside of visible map
* #param cameraBounds Current camera bounds
* #return Latitude and Longitude corrections to get back into bounds.
*/
private LatLng getLatLngCorrection(LatLngBounds cameraBounds) {
double latitude=0, longitude=0;
if(cameraBounds.southwest.latitude < BOUNDS.southwest.latitude) {
latitude = BOUNDS.southwest.latitude - cameraBounds.southwest.latitude;
}
if(cameraBounds.southwest.longitude < BOUNDS.southwest.longitude) {
longitude = BOUNDS.southwest.longitude - cameraBounds.southwest.longitude;
}
if(cameraBounds.northeast.latitude > BOUNDS.northeast.latitude) {
latitude = BOUNDS.northeast.latitude - cameraBounds.northeast.latitude;
}
if(cameraBounds.northeast.longitude > BOUNDS.northeast.longitude) {
longitude = BOUNDS.northeast.longitude - cameraBounds.northeast.longitude;
}
return new LatLng(latitude, longitude);
}
Then the Handler that controls the overscroll (And overzoom) limiting it every 100ms.
/**
* Bounds the user to the overlay.
*/
private class OverscrollHandler extends Handler {
#Override
public void handleMessage(Message msg) {
CameraPosition position = mMap.getCameraPosition();
VisibleRegion region = mMap.getProjection().getVisibleRegion();
float zoom = 0;
if(position.zoom < MIN_ZOOM) zoom = MIN_ZOOM;
if(position.zoom > MAX_ZOOM) zoom = MAX_ZOOM;
LatLng correction = getLatLngCorrection(region.latLngBounds);
if(zoom != 0 || correction.latitude != 0 || correction.longitude != 0) {
zoom = (zoom==0)?position.zoom:zoom;
double lat = position.target.latitude + correction.latitude;
double lon = position.target.longitude + correction.longitude;
CameraPosition newPosition = new CameraPosition(new LatLng(lat,lon), zoom, position.tilt, position.bearing);
CameraUpdate update = CameraUpdateFactory.newCameraPosition(newPosition);
mMap.moveCamera(update);
}
/* Recursively call handler every 100ms */
sendEmptyMessageDelayed(0,100);
}
}
This handler must be defined as a field inside current class (I did this in a class that extends SupportMapFragment)
private OverscrollHandler mOverscrollHandler = new OverscrollHandler();
And lastly it must be called for the first time, i did it at the end of onActivityCreated to be sure that map exists.
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mContext = getActivity();
mMap = getMap();
mMap.setMapType(GoogleMap.MAP_TYPE_NONE);
mMap.addTileOverlay(new TileOverlayOptions().tileProvider(new VexLocalTileProvider(getResources().getAssets())));
CameraUpdate upd = CameraUpdateFactory.newLatLngZoom(new LatLng(41.87145, 12.52849), 14);
mMap.moveCamera(upd);
mOverscrollHandler.sendEmptyMessageDelayed(0,100);
}
Hope you will find it useful!
Instead of using new and shiny push technology which is onCameraChange, you may try to use old poll technology: map.getCameraPosition() or map.getProjection().getVisibleRegion(). You can then check if returned value is what you like and if not, map.moveCamera(...).
Basically you need a Handler, which will get the value of camera position in handleMessage and you send messages to this handler every 10ms or so. Inside handleMessage do sendEmptyMessageDelayed.
You can also use Runnable instead of Message (but that's a matter of taste).
limit zoom you can user this code
private GoogleMap mMap;
// Set a preference for minimum and maximum zoom.
mMap.setMinZoomPreference(6.0f);
mMap.setMaxZoomPreference(14.0f);
In maps API v2 there's a Min/MaxZoomLevel on the GoogleMap class, but I don't know if you can set it in any way.
Another approach would be to add a
GoogleMap.OnCameraChangeListener
to your map and by implementing
public void onCameraChange(CameraPosition cameraPosition);
To restrict the visible area, using GoogleMap.moveCamera(cameraPosition)
That is if you wish the user to be able to scroll or zoom "some".
You can also totally deactivate the scroll/zoom events via GoogleMapOptions