Search code examples
openlayers

OpenLayers: How can I stop the click selection from passing through a higher layer?


If I click on the green rectangle, which is on a layer with a zindex of 4 in the smallVectorLayer, it is not selected because I have set the Select layers to layers: [vectorLayer]. vectorLayer is where the two red rectangles are located and it has a zindex of 3.

It is good that the green rectangle is not selected.

The problem is that the red rectangle under the green one is selected.

The selection process should have stopped at the green rectangle because that is where the click happened. Instead, the selection process continues to the lower layer.

What do I need to change so the red rectangle underneath the green one is not selected when the green rectangle is clicked?

function createRectangle(polygons) {
  const features = [];

  for (const [i, polygon] of polygons.entries()) {
    const feature = {
      type: "Feature",
      geometry: {
        type: "Polygon",
        coordinates: polygon
      },
      properties: {
        id: i
      }
    };

    features.push(feature);
  }

  const json = {
    type: "FeatureCollection",
    features
  };

  return json;
}

const bigRectangle = createRectangle([
  [
    [
      [40, 30],
      [40, 35],
      [70, 35],
      [70, 30]
    ]
  ],
  [
    [
      [40, 20],
      [40, 25],
      [70, 25],
      [70, 20]
    ]
  ]
]);

const styles = {
  Polygon: new ol.style.Style({
    stroke: new ol.style.Stroke({
      color: "hsla(0, 50%, 100%, 1.0)",
      width: 5
    }),
    fill: new ol.style.Fill({
      color: "hsla(0, 50%, 50%, 0.3)"
    })
  })
};

const styleFunction = function (feature) {
  const featureType = feature.getGeometry().getType();
  return styles[featureType];
};

const vectorSource = new ol.source.Vector({
  features: new ol.format.GeoJSON({
    featureProjection: "EPSG:4326" // 4326 3857
  }).readFeatures(bigRectangle)
});

const vectorLayer = new ol.layer.Vector({
  source: vectorSource,
  style: styleFunction,
  zindex: 3
});

const smallRectangle = createRectangle([
  [
    [
      [53, 31],
      [53, 34],
      [57, 34],
      [57, 31]
    ]
  ]
]);

const smallStyles = {
  Polygon: new ol.style.Style({
    stroke: new ol.style.Stroke({
      color: "hsla(120, 50%, 100%, 1.0)",
      width: 5
    }),
    fill: new ol.style.Fill({
      color: "hsla(120, 50%, 50%, 0.3)"
    })
  })
};

const smallStyleFunction = function (feature) {
  const featureType = feature.getGeometry().getType();
  return smallStyles[featureType];
};

const smallVectorSource = new ol.source.Vector({
  features: new ol.format.GeoJSON({
    featureProjection: "EPSG:4326" // 4326 3857
  }).readFeatures(smallRectangle)
});

const smallVectorLayer = new ol.layer.Vector({
  source: smallVectorSource,
  style: smallStyleFunction,
  zindex: 4
});

const map = new ol.Map({
  target: "map",
  layers: [
    new ol.layer.Tile({
      source: new ol.source.OSM()
    }),
    vectorLayer,
    smallVectorLayer
  ],
  view: new ol.View({
    projection: "EPSG:4326",
    center: [55, 25],
    zoom: 5
  })
});

const selected = new ol.style.Style({
  fill: new ol.style.Fill({
    color: "hsla(270, 50%, 50%, .1)"
  }),
  stroke: new ol.style.Stroke({
    color: "hsla(270, 50%, 50%, 1)",
    width: 5
  })
});

function selectStyle(feature) {
  const color = feature.get("COLOR") || "hsla(270, 50%, 50%, .1)";
  selected.getFill().setColor(color);
  return selected;
}

const selectSingleClick = new ol.interaction.Select({
  style: selectStyle,
  layers: [vectorLayer]
});

selectSingleClick.on("select", function (e) {
  console.log("select event",);
});


map.addInteraction(selectSingleClick);
#map {
  height: 512px;
  width: 1024px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/openlayers/7.1.0/ol.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/openlayers/7.1.0/dist/ol.min.js"></script>

<div id="map"></div>


Solution

  • You could use a custom condition which checks if there are features in other layers

    function createRectangle(polygons) {
      const features = [];
    
      for (const [i, polygon] of polygons.entries()) {
        const feature = {
          type: "Feature",
          geometry: {
            type: "Polygon",
            coordinates: polygon
          },
          properties: {
            id: i
          }
        };
    
        features.push(feature);
      }
    
      const json = {
        type: "FeatureCollection",
        features
      };
    
      return json;
    }
    
    const bigRectangle = createRectangle([
      [
        [
          [40, 30],
          [40, 35],
          [70, 35],
          [70, 30]
        ]
      ],
      [
        [
          [40, 20],
          [40, 25],
          [70, 25],
          [70, 20]
        ]
      ]
    ]);
    
    const styles = {
      Polygon: new ol.style.Style({
        stroke: new ol.style.Stroke({
          color: "hsla(0, 50%, 100%, 1.0)",
          width: 5
        }),
        fill: new ol.style.Fill({
          color: "hsla(0, 50%, 50%, 0.3)"
        })
      })
    };
    
    const styleFunction = function (feature) {
      const featureType = feature.getGeometry().getType();
      return styles[featureType];
    };
    
    const vectorSource = new ol.source.Vector({
      features: new ol.format.GeoJSON({
        featureProjection: "EPSG:4326" // 4326 3857
      }).readFeatures(bigRectangle)
    });
    
    const vectorLayer = new ol.layer.Vector({
      source: vectorSource,
      style: styleFunction,
      zindex: 3
    });
    
    const smallRectangle = createRectangle([
      [
        [
          [53, 31],
          [53, 34],
          [57, 34],
          [57, 31]
        ]
      ]
    ]);
    
    const smallStyles = {
      Polygon: new ol.style.Style({
        stroke: new ol.style.Stroke({
          color: "hsla(120, 50%, 100%, 1.0)",
          width: 5
        }),
        fill: new ol.style.Fill({
          color: "hsla(120, 50%, 50%, 0.3)"
        })
      })
    };
    
    const smallStyleFunction = function (feature) {
      const featureType = feature.getGeometry().getType();
      return smallStyles[featureType];
    };
    
    const smallVectorSource = new ol.source.Vector({
      features: new ol.format.GeoJSON({
        featureProjection: "EPSG:4326" // 4326 3857
      }).readFeatures(smallRectangle)
    });
    
    const smallVectorLayer = new ol.layer.Vector({
      source: smallVectorSource,
      style: smallStyleFunction,
      zindex: 4
    });
    
    const map = new ol.Map({
      target: "map",
      layers: [
        new ol.layer.Tile({
          source: new ol.source.OSM()
        }),
        vectorLayer,
        smallVectorLayer
      ],
      view: new ol.View({
        projection: "EPSG:4326",
        center: [55, 25],
        zoom: 5
      })
    });
    
    const selected = new ol.style.Style({
      fill: new ol.style.Fill({
        color: "hsla(270, 50%, 50%, .1)"
      }),
      stroke: new ol.style.Stroke({
        color: "hsla(270, 50%, 50%, 1)",
        width: 5
      })
    });
    
    function selectStyle(feature) {
      const color = feature.get("COLOR") || "hsla(270, 50%, 50%, .1)";
      selected.getFill().setColor(color);
      return selected;
    }
    
    const selectSingleClick = new ol.interaction.Select({
      condition: function(e) {
        return ol.events.condition.singleClick(e) &&
        map.getFeaturesAtPixel(e.pixel, {
          layerFilter: function(l) {
            //return l !== vectorLayer;
            return l.getZIndex() > vectorLayer.getZIndex()
          }
        }).length === 0
      },
      style: selectStyle,
      layers: [vectorLayer]
    });
    
    selectSingleClick.on("select", function (e) {
      console.log("select event",);
    });
    
    
    map.addInteraction(selectSingleClick);
    #map {
      height: 512px;
      width: 1024px;
    }
    <link href="https://cdnjs.cloudflare.com/ajax/libs/openlayers/7.1.0/ol.min.css" rel="stylesheet"/>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/openlayers/7.1.0/dist/ol.min.js"></script>
    
    <div id="map"></div>

    The Select interaction will select the top feature in the layer. You can test that here https://codesandbox.io/s/translate-features-forked-vtcigv Select a state and drag it above or below another when zIndex is in alphabetic order of state names. Then click on empty are and then on the overlap. Change style.setZIndex(Number(feature.get('name').charCodeAt(0))); to style.setZIndex(100 - Number(feature.get('name').charCodeAt(0))); to make sure zIndex really is being used. So you only need to bother with the zIndex of layers. If you have multiple layers with unique zIndex just change return l !== vectorLayer in my example to return l.getZIndex() > vectorLayer.getZIndex()