limitar el desplazamiento y el zoom de Google Maps Android API v2

He agregado GroundOverlay para mapear y quiero limitar el desplazamiento y el zoom dentro de esta área.

¿Cómo limitar el desplazamiento dentro de algunos límites en los mapas de google de Android?

¿Es posible obtener instantáneamente puntos de movimiento de MapFragment?

Por favor, ayúdame.

Puede ser que sea demasiado tarde, pero esta es mi solución:

  1. Deshabilitado incorporado en los gestos de GoogleMap.

  2. Se agregaron gestos personalizados (para desplazarse, lanzar y escalar).

  3. Comprobando el área permitida al procesar eventos.

  4. Establezca límites / zoom manualmente con las funciones estándar de Map.

Y aquí está mi ejemplo:

[ACTUALIZADO]

Hubo un problema: cuando se recibieron eventos táctiles antes de que se inicializara el mapa.

verificar valores nulos en onInterceptTouchEvent

También descubrí que mi solución es un poco lenta que la función incorporada.

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); } } 

Definición dentro de mi xml-layout de fragmento:

  

En el archivo xml, es posible definir botones de zoom / ubicación personalizados y establecer oyentes de clic para la manipulación manual de la cámara (en este caso, debe verificar MAX_ZOOM y MIN_ZOOM, y verificar si la ubicación actual se encuentra dentro de los límites permitidos).

La restricción de la cámara (¡finalmente!) Se ha agregado como una función como parte del lanzamiento de Google Play Services 9.4 : puede llamar a setLatLngBoundsForCameraTarget(LatLngBounds bounds) para establecer el área de panorámica permitida.

 // 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); 

Puede encontrar una explicación detallada en la documentación: Restringir la panorámica del usuario a un área determinada y una actividad de muestra en GitHub .

Lástima que Google no nos permita interceptar y bloquear al usuario, encontré que la respuesta de MaciejGórski es la que mejor se adapta a mis necesidades.

Quería compartir mi solución (basada en su respuesta) con usted.

Primero definí los límites y el zoom máximo / mínimo:

 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; 

Luego creé esta pequeña función para probar si los límites de la cámara actual están fuera de los límites máximos y devolver la diferencia en latitud y longitud.

 /** * 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); } 

Luego, el controlador que controla el desbordamiento (y overzoom) lo limita cada 100 ms.

 /** * 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); } } 

Este controlador debe definirse como un campo dentro de la clase actual (lo hice en una clase que amplía SupportMapFragment)

 private OverscrollHandler mOverscrollHandler = new OverscrollHandler(); 

Y finalmente debe ser llamado por primera vez, lo hice al final de onActivityCreated para asegurarme de que existe un mapa.

 @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); } 

Espero que lo encuentres útil!

En lugar de utilizar la nueva y shiny tecnología de inserción que está onCameraChange , puede intentar usar la tecnología de encuestas antiguas: map.getCameraPosition() o map.getProjection().getVisibleRegion() . Luego puede verificar si el valor devuelto es el que le gusta y, de no ser así, map.moveCamera(...) .

Básicamente, necesita un Handler , que obtendrá el valor de la posición de la cámara en handleMessage y enviará mensajes a este manejador cada 10ms más o menos. Dentro de handleMessage do sendEmptyMessageDelayed .

También puede usar Runnable lugar de Message (pero eso es cuestión de gustos).

En los mapas API v2 hay un Min / MaxZoomLevel en la clase GoogleMap, pero no sé si puedes configurarlo de alguna manera.

Otro enfoque sería agregar un

 GoogleMap.OnCameraChangeListener 

a su mapa y mediante la implementación

 public void onCameraChange(CameraPosition cameraPosition); 

Para restringir el área visible, usando GoogleMap.moveCamera (cameraPosition)

Eso es si desea que el usuario pueda desplazarse o acercarse “un poco”.

También puede desactivar totalmente los eventos de desplazamiento / zoom a través de GoogleMapOptions

limitar el zoom puede usar este código

 private GoogleMap mMap; // Set a preference for minimum and maximum zoom. mMap.setMinZoomPreference(6.0f); mMap.setMaxZoomPreference(14.0f);