openlayersopenlayers-7

TypeScript + OpenLayers 7: Can't find features on custom layer with forEachFeatureAtPixel() method


I can't find features on a custom layer with the forEachFeatureAtPixel() using the coordinates from a map.on('click') event listener. The process is simple:

  1. I create the map object
  2. I add a custom layer (FeatureLayer) to the map
  3. I draw polygons using a custom control (DrawPolygonButton) and add them to the FeatureLayer source
  4. I want to remove clicked polygon(s) by using a custom control (RemovePolygonButton). The control listens to map click events to get map coordinates. The coordinates are used to find out if any polygons exist on the clicked coordinate. For this, I use the forEachFeatureAtPixel method, but it always returns an empty array.
import Map from 'ol/Map'
import OSM from 'ol/source/OSM'
import Draw from 'ol/interaction/Draw';
import TileLayer from 'ol/layer/Tile'
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import View from 'ol/View'
import { Control } from 'ol/control'
import { Geometry } from 'ol/geom';
import { transform, useGeographic } from 'ol/proj';
import { FeatureLike } from 'ol/Feature';

/**
 * Map component
 */
export default class MapComponent {

    map: Map
    featureLayer: FeatureLayer

    constructor() {
        useGeographic()
        
        this.map = new Map({
            controls: [],
            layers: [
                new TileLayer({
                    source: new OSM(),
                })
            ],
            view: new View({
                center: [0, 0],
                zoom: 2
            }),
            target: "map"
        })

        this.featureLayer = new FeatureLayer()

        // Add Feature Layer
        this.map.addLayer(this.featureLayer)

        // Add Custom Controls to Draw- and Remove Polygon
        this.map.addControl(new DrawPolygonButton(this, this.map))
        this.map.addControl(new RemovePolygonButton(this, this.map))
    }

    public getFeatureLayer(): FeatureLayer {
        return this.featureLayer
    }

    public getMap() {
        return this.map
    }
}

/**
 * This is the layer we draw polygons (features) on
 */
class FeatureLayer extends VectorLayer<VectorSource<Geometry>> {
    constructor() { 
        super({
            source: new VectorSource(),
            properties: {
                title: 'featureLayer'
            }
        })
    }
}

/**
 * A button which starts the drawing process
 */
class DrawPolygonButton extends Control {
    constructor(mapComponent: MapComponent, map: Map) {
        const button = document.createElement('button')
        button.innerHTML = 'Draw Polygon'

        super({ element: button })

        button.addEventListener('click', () => {
            const featureLayerSource = mapComponent.getFeatureLayer()?.getSource()

            if (featureLayerSource) {
                new Polygon(map, featureLayerSource)
            }
        })
    }
}

/**
 * A button which should listen to clicking on the map, and if it hits a polygon on the
 * FeatureLayer it should remove it
 */
class RemovePolygonButton extends Control {
    constructor(mapComponent: MapComponent, map: Map) {
        const button = document.createElement('button')
        button.innerHTML = 'Remove Feature'

        super({ element: button })

        button.addEventListener('click', () => {
            map.on('click', (evt: any) => {
                const featuresInFeatureLayer = mapComponent.getFeatureLayer()?.getSource()?.getFeatures()
                console.log('Features in FeatureLayer: ', featuresInFeatureLayer) // Prints two polygon features

                const coordinate = transform(evt.coordinate, 'EPSG:3857', 'EPSG:4326')
                const pixel = map.getPixelFromCoordinate(coordinate)
    
                const features: FeatureLike[] = []
                map.forEachFeatureAtPixel(pixel, function(feature, layer) {
                  features.push(feature);
                })

                console.log('Features: ', features) // Always shows empty array
            })
        })
    }
}

/**
 * The Polygon will be added to the map
 */
class Polygon extends Draw {
    constructor(map: Map, featureLayerSource: VectorSource) {
        super({
            type: 'Polygon',
            source: featureLayerSource
        })    

        this.on('drawend', () => {
            map.removeInteraction(this)
        })        

        map.addInteraction(this);
    } 
}

enter image description here

It does find the two polygon features on the FeatureLayer, but it never finds the feature(s) on the clicked/selected coordinate when clicking on any of the polygons. I also tried clicking all around the map, but the Feature array is always empty:

enter image description here

Note: I tried put this in a code snippet, but it does not always like TypeScript e.g. when extending ol.Control... so I just pass the code.

Thanks!


Solution

  • You do not need to transform the coordinate so

                const coordinate = transform(evt.coordinate, 'EPSG:3857', 'EPSG:4326')
                const pixel = map.getPixelFromCoordinate(coordinate)
    
                const features: FeatureLike[] = []
                map.forEachFeatureAtPixel(pixel, function(feature, layer) {
                  features.push(feature);
                })
    

    can become

                const pixel = map.getPixelFromCoordinate(evt.coordinate)
    
                const features: FeatureLike[] = []
                map.forEachFeatureAtPixel(pixel, function(feature, layer) {
                  features.push(feature);
                })
    

    but as the event also has a pixel property you can use

                const features: FeatureLike[] = []
                map.forEachFeatureAtPixel(evt.pixel, function(feature, layer) {
                  features.push(feature);
                })