1

I would like to use OpenLayers 3 spyglass example inside my project.

The problem is when I want to use selection for layer, which will be inside spyglass circle. I can't get it working, I don't know how to update it on every user's selection correctly.

Anyone more skilled can help me?

Here is my fiddle with it partially working: http://jsfiddle.net/dkcz/8a3pdavv/

  • The first issue I can see is that what is selected in your select box is just a string with the layer name, not an object representing the layer. You would need to do a call to map.getLayers, check if the layer name matches that of your select box, and then add the pre/post compose events to that. See this discussion for a bit more. – John Powell Dec 1 '14 at 17:02
  • Sorry, I can't help more at this point. I'm still getting my head round OL3. – John Powell Dec 1 '14 at 19:06
4

Here's an example that accomplishes what you want (I think): http://jsfiddle.net/tschaub/Lkgx0qaa/

I've created a map with three layers, only the first one (named roads) is visible:

var roads = new ol.layer.Tile({
  source: new ol.source.BingMaps({key: key, imagerySet: 'Road'})
});

var clipped = {
  imagery: new ol.layer.Tile({
    source: new ol.source.BingMaps({key: key, imagerySet: 'Aerial'}),
    visible: false
  }),
  labeled: new ol.layer.Tile({
    source: new ol.source.BingMaps({key: key, imagerySet: 'AerialWithLabels'}),
    visible: false
  })
};

var map = new ol.Map({
  layers: [roads, clipped.imagery, clipped.labeled],
  target: 'map',
  view: new ol.View({
    center: ol.proj.transform([-109, 46.5], 'EPSG:4326', 'EPSG:3857'),
    zoom: 6
  })
});

The imagery and labeled layers are going to be toggled on/off with radio inputs. When a radio input is checked, the layer is made visible (with layer.setVisible(true)), and listeners are registered for precompose and postcompse events (with layer.on(...)).

In addition, any previously chosen layer will be made invisible (with layer.setVisible(false)), and listeners will be unregistered (with layer.un(...)).

The function that handles this radio change event looks like this:

var chosen;
function onRadioChange(event) {
  if (chosen) {
    chosen.un('precompose', onPreCompose);
    chosen.un('postcompose', onPostCompose);
    chosen.setVisible(false);
  }
  chosen = clipped[event.target.value];
  chosen.on('precompose', onPreCompose);
  chosen.on('postcompose', onPostCompose);
  chosen.setVisible(true);
}

That's really the meat of the solution. The rest pretty much follows the hosted layer-spy.html example.

For completeness, I'll dump the rest below.

// get the pixel position with every move
var mousePosition = null;
map.on('pointermove', function(event) {
  mousePosition = event.pixel
  map.render();
});

// before rendering the layer, do some clipping
function onPreCompose(event) {
  var ctx = event.context;
  var pixelRatio = event.frameState.pixelRatio;
  ctx.save();
  ctx.beginPath();
  if (mousePosition) {
    // only show a circle around the mouse
    ctx.arc(mousePosition[0] * pixelRatio, mousePosition[1] * pixelRatio,
        75 * pixelRatio, 0, 2 * Math.PI);
    ctx.lineWidth = 5 * pixelRatio;
    ctx.strokeStyle = 'rgba(0,0,0,0.5)';
    ctx.stroke();
  }
  ctx.clip();
}

// after rendering the layer, restore the canvas context
function onPostCompose(event) {
  var ctx = event.context;
  ctx.restore();
}

// listen for change events on radio inputs
var inputs = document.querySelectorAll('input[type="radio"]');
for (var i = 0, ii = inputs.length; i < ii; ++i) {
  inputs[i].addEventListener('change', onRadioChange);
}

// kick things off with the first checked radio input
onRadioChange({target: document.querySelector('input[type="radio"]:checked')});
  • Thanks very much. It is almost exactly what I need - except I would like to select my layers from select box with almost 50 options(=layers). I will try to implement your solution for it. – dkocich Dec 4 '14 at 12:25
1

I have tried everything with my (unfortunately limited) JavaScript knowledge to set the clip dynamically on the selected layer. I couldn't manage to save and restore the whole canvas context, neither the layer's one. I have tried to unbind the event from the layer, no luck. Neither strUser.un(evt, func) or evt.stopPropagation() worked out. For this, I considered the clipped layer unrecoverable and came up with a hack to solve this problem.

I have created 3 clipped layers along with the 3 original ones, then removed and added them systematically based on user choice.

HTML input:

<select onChange="change(this.value)" id="mySelect">
    <option label="roads" value="roads" selected></option>
    <option label="imagery" value="imagery"></option>
    <option label="osm" value="osm" selected></option>

Layer creation:

var roads = new ol.layer.Tile({
      source: new ol.source.MapQuest({layer: 'sat'})
  });
  var roads_clipped = new ol.layer.Tile({
      source: new ol.source.MapQuest({layer: 'sat'})
  });

  var imagery = new ol.layer.Tile({
      source: new ol.source.Stamen({
        layer: 'watercolor'
      })
    });
      var imagery_clipped = new ol.layer.Tile({
      source: new ol.source.Stamen({
        layer: 'watercolor'
      })
    });

  var osm = new ol.layer.Tile({
      source: new ol.source.OSM(),
      visible: true
  });
  var osm_clipped = new ol.layer.Tile({
      source: new ol.source.OSM(),
      visible: true
  });

  var map = new ol.Map({
      layers: [roads, imagery, osm_clipped],
      target: 'map',
      view: new ol.View({
          center: ol.proj.transform([-109, 46.5], 'EPSG:4326', 'EPSG:3857'),
          zoom: 6
      })
  });

Note that, I have added the clipped version of the osm layer instead of the normal version, to initialize the spyglass.

Events (mouse position and clipping):

  var mousePosition = null;
  $(map.getViewport()).on('mousemove', function (evt) {
      mousePosition = map.getEventPixel(evt.originalEvent);
      map.render();
  }).on('mouseout', function () {
      mousePosition = null;
      map.render();
  });

  osm_clipped.on('precompose', function(event) {
      var ctx = event.context;
      var pixelRatio = event.frameState.pixelRatio;
      ctx.save();
      ctx.beginPath();
      if (mousePosition) {
          // only show a circle around the mouse
          ctx.arc(mousePosition[0] * pixelRatio, mousePosition[1] * pixelRatio,
          radius * pixelRatio, 0, 2 * Math.PI);
          ctx.lineWidth = 5 * pixelRatio;
          ctx.strokeStyle = 'rgba(0,0,0,0.5)';
          ctx.stroke();
      }
      ctx.clip();
  });

  // after rendering the layer, restore the canvas context
  osm_clipped.on('postcompose', function(event) {
      var ctx = event.context;
      ctx.restore();
  });

  imagery_clipped.on('precompose', function(event) {
      var ctx = event.context;
      var pixelRatio = event.frameState.pixelRatio;
      ctx.save();
      ctx.beginPath();
      if (mousePosition) {
          ctx.arc(mousePosition[0] * pixelRatio, mousePosition[1] * pixelRatio,
          radius * pixelRatio, 0, 2 * Math.PI);
          ctx.lineWidth = 5 * pixelRatio;
          ctx.strokeStyle = 'rgba(0,0,0,0.5)';
          ctx.stroke();
      }
      ctx.clip();
  });

  imagery_clipped.on('postcompose', function(event) {
      var ctx = event.context;
      ctx.restore();
  });

  roads_clipped.on('precompose', function(event) {
      var ctx = event.context;
      var pixelRatio = event.frameState.pixelRatio;
      ctx.save();
      ctx.beginPath();
      if (mousePosition) {
          ctx.arc(mousePosition[0] * pixelRatio, mousePosition[1] * pixelRatio,
          radius * pixelRatio, 0, 2 * Math.PI);
          ctx.lineWidth = 5 * pixelRatio;
          ctx.strokeStyle = 'rgba(0,0,0,0.5)';
          ctx.stroke();
      }
      ctx.clip();
  });

  roads_clipped.on('postcompose', function(event) {
      var ctx = event.context;
      ctx.restore();
  });

Global variable to store the last clipped layer:

var strUser = document.getElementById('mySelect').value;

Change function:

function change(e) {
    map.removeLayer(eval(strUser + '_clipped'));  //remove the last added clipped layer
    map.getLayers().getArray().splice(0,0,eval(strUser));  //add the normal version of it to the bottom of the stack
    strUser = e;  //get the new selection
    map.removeLayer(eval(strUser));  //remove the normal version of the new selection
    map.addLayer(eval(strUser + '_clipped'));  //add the clipped version
};

Also note, that you can evaluate a string as a variable with eval(), if the variable exists on the same name as the string. Be sure that the <option> values match the variable names for the layers.

Hint: one of the core OL developers, @erilem can be found on this site. Maybe he can provide a proper solution, if you ask him nicely with the link to this question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.