BoundedMapView – A MapView with limits

In the source I attached to my previous post I included another class I recently created which is based on a patch from Marc Kurtz and Zoran Nikolic (see here for details). Actually, it’s more than just based on their code: I copied quite a bit of their work, so credits where credit’s due.

Anyway, essentially the class allows you to set limits to the area that can be viewed. This is useful if you supply an offline map database that only contains a specific area, a city for example. The class itself is called BoundedMapView.java. A class that illustrates the usage is below the break (for information on MBTileProvider see the previous post as well).

RouteMapActivity.java

package com.example.yourproject;

import java.io.File;

import org.osmdroid.DefaultResourceProxyImpl;
import org.osmdroid.tileprovider.IRegisterReceiver;
import org.osmdroid.util.BoundingBoxE6;
import org.osmdroid.views.MapController;

import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.Menu;

import com.example.yourproject.osmdroid.tileprovider.MBTileProvider;
import com.example.yourproject.osmdroid.views.BoundedMapView;

public class RouteMapActivity extends Activity implements IRegisterReceiver {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Create the mapView with an MBTileProvider
        DefaultResourceProxyImpl resProxy;
        resProxy = new DefaultResourceProxyImpl(this.getApplicationContext());
        
        String packageDir = "/com.example.yourproject";
        String path = Environment.getExternalStorageDirectory() + packageDir;
        File file = new File(path, "HollandRoute.mbtiles");

        MBTileProvider provider = new MBTileProvider(this, file);
        BoundedMapView mapView = new BoundedMapView(this, resProxy, provider);

        double north = 52.6297;
        double east  =  5.3496;
        double south = 52.1613;
        double west  =  4.4638;
        BoundingBoxE6 bBox = new BoundingBoxE6(north, east, south, west);

        mapView.setScrollableAreaLimit(bBox);
        mapView.setBuiltInZoomControls(true);

        // Zoom in and go to Amsterdam
        MapController controller = mapView.getController();
        controller.setZoom(12);
        controller.animateTo(new LatLonPoint(52.373444, 4.892229));

        // Set the MapView as the root View for this Activity; done!
        setContentView(mapView);

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_route_map, menu);
        return true;
    }

}

BoundedMapView.java

/**
 * Created on August 12, 2012
 * 
 * @author Melle Sieswerda
 */
package com.example.yourproject.osmdroid.views;

import microsoft.mappoint.TileSystem;

import org.osmdroid.ResourceProxy;
import org.osmdroid.events.ScrollEvent;
import org.osmdroid.tileprovider.MapTileProviderBase;
import org.osmdroid.util.BoundingBoxE6;
import org.osmdroid.views.MapView;
import org.osmdroid.views.util.constants.MapViewConstants;

import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.widget.Scroller;

/**
 * Extension of MapView that limits scrolling to the area specified. Based on
 * code from Marc Kurtz and Zoran Nikolic (see
 * http://code.google.com/u/107017135012155810755/ for details)
 */
public class BoundedMapView extends MapView {

    protected Rect mScrollableAreaLimit;
    protected BoundingBoxE6 box;

    public BoundedMapView(Context context,
                          ResourceProxy resourceProxy,
                          MapTileProviderBase provider) {

        super(context,
              provider.getTileSource().getTileSizePixels(),
              resourceProxy,
              provider);

    }

    /**
     * Set the map to limit it's scrollable view to the specified BoundingBoxE6.
     * Note that, like North/South bounds limiting, this allows an overscroll of
     * half the screen size. This means each border can be scrolled to the
     * center of the screen.
     * 
     * @param box
     *            A lat/long bounding box to limit scrolling to, or null to
     *            remove any scrolling limitations
     */
    public void setScrollableAreaLimit(BoundingBoxE6 box) {

        final int worldSize_2 = TileSystem.MapSize(MapViewConstants.MAXIMUM_ZOOMLEVEL) / 2;

        // Clear scrollable area limit if null passed.
        if (box == null) {
            mScrollableAreaLimit = null;
            return;
        }

        // Get NW/upper-left
        final Point upperLeft = TileSystem.LatLongToPixelXY(box.getLatNorthE6() / 1E6,
                                                            box.getLonWestE6() / 1E6,
                                                            MapViewConstants.MAXIMUM_ZOOMLEVEL,
                                                            null);
        upperLeft.offset(-worldSize_2, -worldSize_2);

        // Get SE/lower-right
        final Point lowerRight = TileSystem.LatLongToPixelXY(box.getLatSouthE6() / 1E6,
                                                             box.getLonEastE6() / 1E6,
                                                             MapViewConstants.MAXIMUM_ZOOMLEVEL,
                                                             null);
        lowerRight.offset(-worldSize_2, -worldSize_2);
        mScrollableAreaLimit = new Rect(upperLeft.x,
                                        upperLeft.y,
                                        lowerRight.x,
                                        lowerRight.y);
    }

    @Override
    public void scrollTo(int x, int y) {
        final int worldSize_2 = TileSystem.MapSize(this.getZoomLevel(true)) / 2;
        while (x < -worldSize_2) {
            x += worldSize_2 * 2;
        }
        while (x > worldSize_2) {
            x -= worldSize_2 * 2;
        }
        if (y < -worldSize_2) {
            y = -worldSize_2;
        }
        if (y > worldSize_2) {
            y = worldSize_2;
        }

        if (mScrollableAreaLimit != null) {
            final int zoomDiff = MapViewConstants.MAXIMUM_ZOOMLEVEL - getZoomLevel();
            final int minX = mScrollableAreaLimit.left >> zoomDiff;
            final int minY = mScrollableAreaLimit.top >> zoomDiff;
            final int maxX = mScrollableAreaLimit.right >> zoomDiff;
            final int maxY = mScrollableAreaLimit.bottom >> zoomDiff;
            if (x < minX)
                x = minX;
            else if (x > maxX)
                x = maxX;
            if (y < minY)
                y = minY;
            else if (y > maxY)
                y = maxY;
        }
        super.scrollTo(x, y);

        // do callback on listener
        if (mListener != null) {
            final ScrollEvent event = new ScrollEvent(this, x, y);
            mListener.onScroll(event);
        }
    }

    @Override
    public void computeScroll() {
        final Scroller mScroller = getScroller();
        final int mZoomLevel = getZoomLevel(false);

        if (mScroller.computeScrollOffset()) {
            if (mScroller.isFinished()) {
                /**
                 * Need to jump through some accessibility hoops here Silly
                 * enough the only thing MapController.setZoom does is call
                 * MapView.setZoomLevel(zoomlevel). But noooo .. if I try that
                 * directly setZoomLevel needs to be set to "protected".
                 * Explanation can be found at
                 * http://docs.oracle.com/javase/tutorial
                 * /java/javaOO/accesscontrol.html
                 * 
                 * This also suggests that if the subclass is made to be part of
                 * the package, this can be replaced by a simple call to
                 * setZoomLevel(mZoomLevel)
                 */
                // This will facilitate snapping-to any Snappable points.
                getController().setZoom(mZoomLevel);
            } else {
                /* correction for double tap */
                int targetZoomLevel = getZoomLevel();
                if (targetZoomLevel == mZoomLevel)
                    scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            }
            postInvalidate(); // Keep on drawing until the animation has
            // finished.
        }
    }

}

Tagged , . Bookmark the permalink.

6 Responses to BoundedMapView – A MapView with limits

  1. jimmy says:

    HIHI
    please can you help, i want this “RouteMapActivity.java” and “BoundedMapView.java” source code . please..
    thank’s.

  2. Tom says:

    This is great. A problem I’m having though is that I set the tile size to 512 even though the tiles are 256. This is so that OSMDroid renders them twice as big on high pixel density screens. Otherwise the maps and labels are too small to see. However, changing the tile size causes BoundedMapView to not work. I feel like there should be an easy way to fix this, but can’t figure it out.

    // this works
    mapView.setTileSource (new XYTileSource (“Mapnik”, ResourceProxy.string.offline_mode, 13, 17, 256, “.png”, “http://127.0.0.1″));

    // this doesn’t work
    mapView.setTileSource (new XYTileSource (“Mapnik”, ResourceProxy.string.offline_mode, 13, 17, 512, “.png”, “http://127.0.0.1″));

  3. warwick says:

    Hi melle

    Great code!! Thank you.

    The only problem I have is when zooming in using the mapview’s buttons. When zooming, the result is that the screen goes to the bottom right of the bounded ares and while zooming out it goes to the top left of the area.

    I have zeroed in on the code that does this: super.scrollTo(x, y); on line 113 (BoundedMapView.java above). I am hoping to work a way around this without changing too much. Any help or comments would be great.

    Thanks again

  4. warwick says:

    Hi melle

    After doing a lot of testing, I realised that it does not need to do relocate when zooming. So I changed the line as follows:

    if (getZoomLevel(true) == getZoomLevel(false))
    super.scrollTo(x, y);

    (I know its a very rough solution – but it works very well.

    Best regards

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>