Restrict Pan outside WMS extent in OpenLayers3
Asked Answered
L

3

15

I have rectangle WMS of small area and want to restrict panning outside WMS extends, so there aren't white or black area outside the map visible at all. Adding extent to View does not work for me and in documentation about this option is written

The extent that constrains the center, in other words, center cannot be set outside this extent.

But as I understand this if center is in the area of extent, but on the very corner, it will show white area outside this extent, but I don't want to see white area at all.

Is it possible to achieve this with OL3?

Lyndsaylyndsey answered 29/11, 2014 at 7:43 Comment(5)
This is a good question. What you want is not yet supported. A while ago I started working on a patch that would cover your use-case, but this is patch isn't merged yet. See <github.com/openlayers/ol3/pull/2777>. I'll try to work on this again.Retaliation
Oh, that is bad. This white area is what I really dislike about majority of online maps I saw. Waiting now for OL3 to support it.Lyndsaylyndsey
Erilem's patch works and you could easily add it yourself. erilem.net/ol3/constrain-center/examples/restricted-extent.jsMephitis
The link to erilem's pull request has a stealthy extra > at the end and gives a 404. Working link: github.com/openlayers/ol3/pull/2777Heartbreaker
I'm not a fan about not being able to see the white area outside the extent. That would mean i can never center on areas that are in the available area. This lends to further issues down the line.Dennison
H
12

Here's my solution. I wrote it just now, and so it is not extensively tested. It would probably break if you start rotating the map, for example, and it may be glitchy if you zoom out too far.

var constrainPan = function() {
    var visible = view.calculateExtent(map.getSize());
    var centre = view.getCenter();
    var delta;
    var adjust = false;
    if ((delta = extent[0] - visible[0]) > 0) {
        adjust = true;
        centre[0] += delta;
    } else if ((delta = extent[2] - visible[2]) < 0) {
        adjust = true;
        centre[0] += delta;
    }
    if ((delta = extent[1] - visible[1]) > 0) {
        adjust = true;
        centre[1] += delta;
    } else if ((delta = extent[3] - visible[3]) < 0) {
        adjust = true;
        centre[1] += delta;
    }
    if (adjust) {
        view.setCenter(centre);
    }
};
view.on('change:resolution', constrainPan);
view.on('change:center', constrainPan);

This expects the variables map, view (with obvious meanings) and extent (the xmin, ymin, xmax, ymax you want to be visible) to be available.

Heartbreaker answered 18/2, 2015 at 21:24 Comment(4)
works great but in full screen mode cause zoom and drag to stop workingUnrig
Yeah, I mentioned that it'd probably break if zoomed out too far. Probably the same situation when you go to full screen. Glad it helps all the same. If you have particular suggestions to make it more reliable please suggest an edit.Heartbreaker
I get "too much recursion" error for a quicker pan, and my layer is disappearing... just for the recordEstrada
Yes; it's definitely not production-ready code for the general case (though it worked fine for my use case). Any improvements are welcome.Heartbreaker
P
2

Here's a more robust implementation that should work really well in any case. It's written in ES6, and requires isEqual method (from lodash or anything else ...)

const extent = [-357823.2365, 6037008.6939, 1313632.3628, 7230727.3772];
const view = this.olMap.getView();

const modifyValues = {};

// Trick to forbid panning outside extent
let constrainPan = (e) => {
  const type = e.type;
  const newValue = e.target.get(e.key);
  const oldValue = e.oldValue;

  if (isEqual(oldValue, newValue)) {
    // Do nothing when event doesn't change the value
    return;
  }

  if (isEqual(modifyValues[type], newValue)) {
    // Break possible infinite loop
    delete modifyValues[type];
    return;
  }

  if (type === 'change:resolution' && newValue < oldValue) {
    // Always allow zoom-in.
    return;
  }

  const visibleExtent = view.calculateExtent(this.olMap.getSize());
  const intersection = ol.extent.getIntersection(visibleExtent, extent);
  const modify = !isEqual(intersection, visibleExtent);

  if (modify) {
    if (type === 'change:center') {
      const newCenter = newValue.slice(0);

      if (ol.extent.getWidth(visibleExtent) !== ol.extent.getWidth(intersection)) {
        newCenter[0] = oldValue[0];
      }

      if (ol.extent.getHeight(visibleExtent) !== ol.extent.getHeight(intersection)) {
        newCenter[1] = oldValue[1];
      }

      modifyValues[type] = newCenter;
      view.setCenter(newCenter);
    } else if (type === 'change:resolution') {
      modifyValues[type] = oldValue;
      view.setResolution(oldValue);
    }
  }
};
view.on('change:resolution', constrainPan);
view.on('change:center', constrainPan);
Photomicrograph answered 9/10, 2017 at 12:57 Comment(1)
ES6 lodash import note: make sure you do import { isEqual } from 'lodash'. If you forget the curly braces it will not throw an error, but isEqual() will always return true for arrays.Florentinoflorenza
D
1

This is an extension to @tremby answer, but to long for a comment.

First of all, his solution works really well for me, but it was called way to often. Therefore I wrapped it in a debounce function.

So

view.on('change:resolution', constrainPan);
view.on('change:center', constrainPan);

becomes

var dConstrainPan = debounce(constrainPan);
view.on('change:resolution', dConstrainPan);
view.on('change:center', dConstrainPan);

This will result in a slight flicker, when moving outside the bounding box, bot zooming/ moving works without delay.

Still not perfect but a useful improvement from my point of view.

Debounce code:

// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
    var timeout;
    return function() {
        var context = this, args = arguments;
        var later = function() {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        var callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
};

Soruce: https://davidwalsh.name/javascript-debounce-function , in underscore.js

Dexamethasone answered 10/3, 2017 at 10:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.