Ограничить прокрутку и масштабирование API Google Maps Android v2

Я добавил GroundOverlay, чтобы отобразить и ограничить прокрутку и масштабирование в этой области.

Как ограничить прокрутку в некоторых границах на картах Google Android?

Можно ли мгновенно получить точки движения из MapFragment?

Пожалуйста, помогите мне.

Ответ 1

Может быть, уже слишком поздно, но вот мое решение:

  • Отключено встроенные жесты GoogleMap.

  • Добавлены пользовательские жесты (для прокрутки, прокрутки и масштабирования).

  • Проверка разрешенной области при обработке событий.

  • Установите границы/масштабирование вручную со стандартными функциями карты.

И вот мой пример:

[ОБНОВЛЕНО]

Возникла проблема - когда события касания были получены до инициализации карты.

проверить значения null в onInterceptTouchEvent

Также я обнаружил, что мое решение немного медленнее, чем встроенная функция.

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

Определение в моем XML-макете фрагмента:

<com.package....RestrictedMapView
    android:id="@+id/mapview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" /> 

В xml файле можно определить пользовательские кнопки масштабирования/местоположения и настроить прослушиватели кликов для ручного управления камерой (в этом случае вам необходимо проверить MAX_ZOOM и MIN_ZOOM и проверить, находится ли текущее местоположение в пределах разрешенных границ).

Ответ 2

Сдерживание камеры (наконец!) было добавлено как функция как часть выпуска Служб Google Play 9.4 - вы можете вызвать setLatLngBoundsForCameraTarget(LatLngBounds bounds), чтобы установить разрешенную область панорамирования.

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

В документации вы найдете подробное объяснение: Ограничение панорамирования пользователя в данной области и активность примера в GitHub.

Ответ 3

Слишком плохо, что Google не позволяет нам перехватывать и блокировать пользователя, я обнаружил, что ответ MaciejGórski тот, который наилучшим образом соответствует моим потребностям.

Я хотел поделиться с вами своим решением (исходя из его ответа).

Сначала я определил границы и максимальное/минимальное масштабирование:

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;

Затем я создал эту небольшую функцию, чтобы проверить, не ограничены ли текущие границы камеры пределами максимальных границ и возвращают разницу в широте и долготе.

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

Затем обработчик, который управляет надстройкой (и overzoom), ограничивая ее каждые 100 мс.

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

Этот обработчик должен быть определен как поле внутри текущего класса (я сделал это в классе, который расширяет SupportMapFragment)

private OverscrollHandler mOverscrollHandler = new OverscrollHandler();

И, наконец, он должен быть вызван в первый раз, я сделал это в конце onActivityCreated, чтобы убедиться, что карта существует.

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

Надеюсь, вы найдете это полезным!

Ответ 4

Вместо использования новой и блестящей технологии push onCameraChange вы можете попробовать использовать старую технологию опроса: map.getCameraPosition() или map.getProjection().getVisibleRegion(). Затем вы можете проверить, понравилось ли возвращаемое значение, а если нет, map.moveCamera(...).

В основном вам нужен Handler, который получит значение положения камеры в handleMessage, и вы отправляете сообщения этому обработчику каждые 10 мс или около того. Внутри handleMessage do sendEmptyMessageDelayed.

Вы также можете использовать Runnable вместо Message (но это вопрос вкуса).

Ответ 5

В maps API v2 есть класс Min/MaxZoomLevel в классе GoogleMap, но я не знаю, можете ли вы его каким-либо образом установить.

Другим подходом было бы добавить

GoogleMap.OnCameraChangeListener

на вашу карту и путем реализации

public void onCameraChange(CameraPosition cameraPosition);

Чтобы ограничить видимую область, используя GoogleMap.moveCamera(cameraPosition)

То есть, если вы хотите, чтобы пользователь мог прокручивать или приближать "некоторые".

Вы также можете полностью отключить события прокрутки/трансляции через GoogleMapOptions

Ответ 6

limit zoom вы можете использовать этот код

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