Search code examples
openlayers

How can I bring an OpenLayers feature to the front when selected?


When I select one of the squares, I need to be able to bring that feature to the front. If I don't bring it to the front, it looks like:

first square selected

and the right edge of the square is hidden behind the square to the right.

I did find one possible solution which was to use:

    const tmp = e.selected[0].clone();
    vectorLayer.getSource().removeFeature(e.selected[0]);
    vectorLayer.getSource().addFeature(tmp);

which removes the selected feature and adds it back in. When added back in, it is in front of everything else. This works when I select one of the squares, but fails when I select the second. Selecting the second should set the first back to the deselected state, but that no longer happens. It looks like:

two selected

What do I need to change so that I can show one of the squares as selected and in front?

const squares = {
  type: "FeatureCollection",
  features: [
    {
      type: "Feature",
      geometry: {
        type: "Polygon",
        coordinates: [
          [
            [40, 20],
            [40, 30],
            [50, 30],
            [50, 20]
          ]
        ]
      }
    },
    {
      type: "Feature",
      geometry: {
        type: "Polygon",
        coordinates: [
          [
            [50, 20],
            [50, 30],
            [60, 30],
            [60, 20]
          ]
        ]
      }
    },
    {
      type: "Feature",
      geometry: {
        type: "Polygon",
        coordinates: [
          [
            [60, 20],
            [60, 30],
            [70, 30],
            [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(squares)
});

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

const map = new ol.Map({
  target: "map",
  layers: [
    new ol.layer.Tile({
      source: new ol.source.OSM()
    }),
    vectorLayer
  ],
  view: new ol.View({
    projection: "EPSG:4326",
    center: [60, 20],
    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 });

selectSingleClick.on("select", function (e) {
  if (e.selected.length > 0) {
    console.log("select event");

    const tmp = e.selected[0].clone();
    vectorLayer.getSource().removeFeature(e.selected[0]);
    vectorLayer.getSource().addFeature(tmp);
  }
});

map.addInteraction(selectSingleClick);
#map {
  height: 450px;
}
<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

  • The selected style should have a zIndex value higher than that of the unselected style used for the layer. Default is 0 so anything higher will work.

    const squares = {
      type: "FeatureCollection",
      features: [
        {
          type: "Feature",
          geometry: {
            type: "Polygon",
            coordinates: [
              [
                [40, 20],
                [40, 30],
                [50, 30],
                [50, 20]
              ]
            ]
          }
        },
        {
          type: "Feature",
          geometry: {
            type: "Polygon",
            coordinates: [
              [
                [50, 20],
                [50, 30],
                [60, 30],
                [60, 20]
              ]
            ]
          }
        },
        {
          type: "Feature",
          geometry: {
            type: "Polygon",
            coordinates: [
              [
                [60, 20],
                [60, 30],
                [70, 30],
                [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(squares)
    });
    
    const vectorLayer = new ol.layer.Vector({
      source: vectorSource,
      style: styleFunction
    });
    
    const map = new ol.Map({
      target: "map",
      layers: [
        new ol.layer.Tile({
          source: new ol.source.OSM()
        }),
        vectorLayer
      ],
      view: new ol.View({
        projection: "EPSG:4326",
        center: [60, 20],
        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
      }),
      zIndex: 1
    });
    
    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 });
    map.addInteraction(selectSingleClick);
    #map {
      height: 450px;
    }
    <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>