Leaflet / Leaflet.VectorGrid

Display gridded vector data (sliced GeoJSON or protobuf vector tiles) in Leaflet 1.0.0
598 stars 194 forks source link

Interactivity on overlapping vectorgrid layers (and individual features). #158

Closed dbauszus-glx closed 6 years ago

dbauszus-glx commented 6 years ago

Is there a way to highlight/select features on overlapping vectorgrid layers?

Currently the interactivity only works with vectorgrid layer on the topmost pane. Events on vectorgrid layers on panes below are not triggered.

With L.geojson layers I can solve this by events on each feature.

I also would like to control which items on a vectorgrid layer should be interactive. Currently I can only control whether the complete vectorgrid layer is interactive or not.

chriszrc commented 6 years ago

When you say "overlapping vectorgrid layers", do you mean you're creating more than one vectorgrid layer instance (like L.vectorGrid.protobuf() several times) or just multiple layers within the one vectorgrid instance?

I found that as long as some of the overlapping geometries were exposed, I could mouse over that portion (I have all the layers in one vectorgrid layer instance).

It sounds like maybe you might want to consider combining the layers into one vector grid tile first, if you're not doing that already.

As for interactivity, I found this problematic as well, I would love for someone to chime in here, but I could not find any documented way to get the layer name of a feature from the callback. I had to introduce my own function to scan the private _vectorTiles collection:

let serviceAreas = L.vectorGrid.protobuf(...);
serviceAreas.on({
      'click':(e:LeafletMouseLayerEvent) => {
        let layerName = this.getVectorGridLayerNameById(serviceAreas,e.layer.properties.i);

        if(layerName === "somelayerIdontwantinteractivitywith"){
          return;
        }

       //rest
});

let getVectorGridLayerNameById = (vectorGrid:any, id:number)=>{
    let layerName = "";

    //TODO there appears there is no way to get the layer name of clicked feature, we have to look it up from the collection of the currently displayed vector tiles :(
    //TODO obviously we're accessing private properties of the vectorGrid class, this is not ideal
    for(let k of Object.values(vectorGrid._vectorTiles)){
      if(k._features[id]){
        layerName = k._features[id].layerName;
        break;
      }
    }

    return layerName;
  }
tomchadwin commented 6 years ago

Are there no properties of the event object that is passed to the handler?

chriszrc commented 6 years ago

Hi, here's what you get in the event handler:

containerPoint:x {x: 401.70630136034333, y: 408.6248820430993}
latlng:M {lat: 40.79457880806619, lng: -75.64248247543757}
layer:e {properties: {i: 1428, c: 5}, _point: x, _empty: ƒ, _initHooksCalled: true, getLatLng: null, …}
layerPoint:x {x: 650.7063013603433, y: 408.6248820430993}
originalEvent:MouseEvent {isTrusted: true, screenX: 2175, screenY: 529, clientX: 735, clientY: 455, …}
propagatedFrom:e {properties: {…}, _point: x, _empty: ƒ, _initHooksCalled: true, getLatLng: null, …}
sourceTarget:e {properties: {…}, _point: x, _empty: ƒ, _initHooksCalled: true, getLatLng: null, …}
target:e {_url: "http://localhost:3000/v1/tiles/{x}/{y}/{z}?l=mining&l=impaired", options: {…}, _vectorTiles: {…}, _overriddenStyles: {…}, _events: {…}, …}
type:"click"
__proto__:Object

The "layer" entry is not the layer you clicked, but the individual feature. No where is there the layer name. However, the workaround is not so bad, it's very efficient to check the vectortiles object/hashmap for the presence of the id-

dbauszus-glx commented 6 years ago

@chriszrc , the target is the layer if I understand you correct.

For example this prints true:

layer.L = L.vectorGrid.protobuf(url, options).on('click', (e) => console.log(e.target === layer.L));

I create multiple vectorGrid layer. Some come from different datasources.

For example we have an OSM roads layer and a layer with client data. Only the topmost feature (last added or superior pane) can be interacted with. It will not be easily feasible to merge the different protobuf sources into a single layer.

chriszrc commented 6 years ago

@dbauszus-glx Hi, yes, that's correct, the e.target is indeed the protobuf "layer". But for controlling the interactivity with "layers" within the protobuf collection (there are too many things called layers here, in my protobuf tiles there are several different collections of "layers", which are named, like "roads" or "rivers", etc"), you need to figure out what internal protobuf layer they belong to, in order to add the right kind of effect.

For instance, I want the rivers to get more wide and blue on mouseover, or highlight the roads yellow or something, you would need to know which collection the feature came from. The function I posted above does just that, and it's actually very efficient, because of how the features are stored internally in the protobuf layer, but it still seems like there ought to be an official way to access these-

dbauszus-glx commented 6 years ago

I just put together a jsfiddle for this problem.

Please have a look at https://jsfiddle.net/goldrydigital/g3mtbxqz/

What I am trying to do is to select nuts regions (grey) which have been added first. But I can only ever select the cities(pink) which are on top.

chriszrc commented 6 years ago

Hi, yeah, I think this issue was already discussed here:

https://github.com/Leaflet/Leaflet.VectorGrid/issues/88

If you're using the canvas renderer, only the topmost protobuf layer gets interactivity. But I'm using the canvas renderer in my project, and if you can get the data into one protobuf tile layer, then you do get interactivity with all the features in different "layers" within the one protobuf layer.

I'm curious, what's the reason you can't combine them?

dbauszus-glx commented 6 years ago

Just switching to SVG renderer does seem to do the trick. Was kind of obvious but I didn't really think much about it as I copied the renderer setting from an example. I will close this.

For reference I have changed the fiddle to use L.svg.tile as renderer now.

mngyng commented 2 years ago

I did some workaround and made it work for canvas layers. Not a pretty one, but works. Read my comment here: https://gist.github.com/perliedman/84ce01954a1a43252d1b917ec925b3dd