Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Limit Panning OpenLayers 5

I'm using OpenLayers 5 for my project and wasn't able to find anything on here for that. In the past, OpenLayers has had restrictExtent, but that seems to have disappeared. Is there a way to limit or restrict the user's ability to pan vertically so that they can't drag it off the screen?

I've tried using the maxExtent on both the layer containing the map and the View, and I've been able to limit what's visible by doing so on the View. I haven't been able to limit the panning, however. Any help with this would be awesome.

map.js

componentDidMount() {
        BASE_VIEW.setMinZoom(this.getMinZoom());
        this.resize();
        this.map = new Map({
            controls: defaultControls().extend([MOUSE_POSITION_CONTROL]),
            layers: [BASE_LAYER],
            target: 'map',
            view: BASE_VIEW
        });
    }

map-constants.js

const baseView = new View({
    center: fromLonLat(conus, projection),
    projection: projection,
    zoom: 4,
    extent: [Number.NEGATIVE_INFINITY, -43, Number.POSITIVE_INFINITY, 43]
});
like image 788
Andrew Avatar asked Feb 06 '19 20:02

Andrew


1 Answers

The nearest equivalent to OpenLayers 2 restrictedExtent in OL3/4/5 is the misleadingly named extent option of ol.View but that is a center constraint which restricts the map center, and depending on resolution and map size it is possible to pan the edges of the map considerably beyond that.

To replicate restrictedExtent you would need to recalculate the center constraint and reset the view as the resolution changes (ignoring very small changes to avoid performance issues). Assuming you have opened the map at the required maximum extent (using .fit() perhaps) you could then use this code

var extent = map.getView().calculateExtent(); // opening coverage
var resolution = 0;

function resChange() {
    var newResolution = map.getView().getResolution();
    if (resolution == 0 || Math.abs((newResolution-resolution)/resolution) > 0.01) {
        resolution = newResolution;
        var width = map.getSize()[0] * resolution;
        var height = map.getSize()[1] * resolution;
        var view = new ol.View({
            projection: map.getView().getProjection(),
            extent: [extent[0]+(width/2), extent[1]+(height/2), extent[2]-(width/2), extent[3]-(height/2)],
            center: map.getView().getCenter(),
            resolution: resolution,
            maxZoom: map.getView().getMaxZoom(),
            minZoom: map.getView().getMinZoom()
        });
        view.on('change:resolution', resChange);
        map.setView(view);
    }
}
resChange();

Here's a demo. For efficiency I'm only resetting the view at integer zoom levels (resulting in a stretch and snap back effect when zooming out close to the edges), and for the demo to work full page I reset the map when resized.

var view = new ol.View();

var map = new ol.Map({
    layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }) ],
    target: 'map'
});

function openMap() {

    map.setView(view);
    var extent = ol.proj.transformExtent([ -2, 50.5, 2, 53], 'EPSG:4326', 'EPSG:3857');

    // fit the extent horizontally or vertically depending on screen size
    // to determine the maximum resolution possible
    var size = map.getSize();
    var width = ol.extent.getWidth(extent);
    var height = ol.extent.getHeight(extent);
    if (size[0]/size[1] > width/height) {
        view.fit([extent[0],(extent[1]+extent[3])/2,extent[2],(extent[1]+extent[3])/2], { constrainResolution: false });
    } else {
        view.fit([(extent[0]+extent[2])/2,extent[1],(extent[0]+extent[2])/2,extent[3]], { constrainResolution: false });
    }
    var maxResolution = view.getResolution();
    var resolution = 0;

    function resChange() {
        var oldView = map.getView();
        if (resolution == 0 || oldView.getZoom()%1 == 0) {
            resolution = oldView.getResolution();
            var width = map.getSize()[0] * resolution;
            var height = map.getSize()[1] * resolution;
            var newView = new ol.View({
                projection: oldView.getProjection(),
                extent: [extent[0]+(width/2), extent[1]+(height/2), extent[2]-(width/2), extent[3]-(height/2)],
                resolution: resolution,
                maxResolution: maxResolution,
                rotation: oldView.getRotation() 
            });
            newView.setCenter(newView.constrainCenter(oldView.getCenter()));
            newView.on('change:resolution', resChange);
            map.setView(newView);
        }
    }
    resChange();

}
map.on('change:size', openMap);
openMap();
<link rel="stylesheet" href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" type="text/css">
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<div id="map" class="map"></div>

UPDATE

In OpenLayers 6 the view extent option does restrict the extent and not the center (useless constrainOnlyCenter is also specified) so no workaround is needed.

like image 174
Mike Avatar answered Nov 02 '22 08:11

Mike