0

I've implemented OpenLayers 6 with a Select interaction and a Draw interaction, which I toggle between.

We can assume that the user is only drawing polygons (geometry of three points or more).

I want the polygon drawn to be selected once it is completed.

So, I listen for 'drawend' on the Draw interaction, set the draw interaction to inactive, set the select interaction to active, but then I need to set the selected features to be only the drawn feature.

So far, all I have is this clunky hack that I came up with:

// Find the center point of the drawn feature (EPSG:3857)
const featureCenter: [number, number] = this._getCenterOfExtent(geometry.getExtent());
// Get the local pixel coordinate for the feature's center
const pixel: Pixel = this._map.getPixelFromCoordinate(featureCenter);
// Create a new MapBrowserEvent registering a 'click' on this local pixel coordinate
const mbEvent: MapBrowserEvent = new MapBrowserEvent('click', this._map, new MouseEvent('click', {
    bubbles: true,
    cancelable: true,
    clientX: pixel[0],
    clientY: pixel[1]
}));
// Turn off multi-polygon selection on our Select Interaction. We only want the top polygon at this spot.
this._registeredSelector.setProperties({
    multi: false
});
// Trigger the 'click' event on the map
this._map.dispatchEvent(mbEvent);

Unfortunately, this doesn't always work. I'm listening for 'click' on my Select interaction, and it does fire after forcibly triggering a click. Sometimes, though, the event has multiple geometries of Point, LineString, and Polygon. Many times, it doesn't even have a Polygon geometry in the selected array. Other times, it has a nearby Polygon geometry in the selected array, and not the one we last drew.

Is there an OpenLayers native method for forcibly triggering the selection of a specific feature?

Also, here is my _getCenterOfExtent method:

/* EXPECTS EPSG:3857 */
private _getCenterOfExtent(extent: Extent):[number, number] {
    return [
        extent[0] + (extent[2]-extent[0])/2,
        extent[1] + (extent[3]-extent[1])/2
    ];
}

UPDATE Using the screen coordinates for my click event against the window object appears to work more accurately than attempting client coordinates against the map. Also, I found that programmatically toggling multi on the Select Interaction does not actual work, at least not the way I was trying. I switched to always use single select, which should meet our needs.

const bbox: ClientRect | DOMRect = this._map.getTargetElement().getBoundingClientRect();
let mbEvent: MapBrowserEvent | undefined;
if (bbox) {
    pixel[0] += bbox.left;
    pixel[1] += bbox.top;
    mbEvent = new MapBrowserEvent('click', this._map, new MouseEvent('click', {
        view: window,
        bubbles: true,
        cancelable: true,
        screenX: pixel[0],
        screenY: pixel[1]
    }));
} else {
    mbEvent = new MapBrowserEvent('click', this._map, new MouseEvent('click', {
        bubbles: true,
        cancelable: true,
        clientX: pixel[0],
        clientY: pixel[1]
    }));
}
this._map.dispatchEvent(mbEvent);
WebWanderer
  • 10,380
  • 3
  • 32
  • 51

1 Answers1

0

I found my answer in a pretty obscure place. This was an old answer to an old question about fitting a viewport to a selected set of polygons.

It appears that you can modify a collection by reference and the map will update to reflect the changes.

Therefore, we can get the Collection of selected features from a Select Interaction, clear the contents, and set the new contents.

import { Collection } from 'ol';

...

type RegisteredListener = (event: MapBrowserEvent | DrawEvent | SelectEvent) => boolean;

...

private _drawingSource: VectorSource = new VectorSource({wrapX: false});
private _drawingLayer: VectorLayer = new VectorLayer({
    source: this._drawingSource
});
private _registeredDraw: Draw = new Draw({
    source: this._drawingSource,
    type: 'Polygon'
});
private _registeredSelector: Select = new Select({
    condition: click,
    multi: false,
    filter: (feature: FeatureLike, layer: Layer) => {
        const geometry: RenderFeature | Geometry | undefined = feature.getGeometry();
        if (geometry && geometry.getType() === 'Polygon') {
            return true;
        }
        return false;
    }
});

...

private _onDrawEnd: RegisteredListener = (event: DrawEvent) => {
    if (event.feature) {
        // Get Collection of selected Features
        const selectedCollection: Collection<FeatureLike> = this._registeredSelector.getFeatures();
        // Clear the Collection
        selectedCollection.clear();
        // Add the recently drawn Feature to the collection
        selectedCollection.push(event.feature);
        event.stopPropagation();
        // Re-enable the Select Interaction
        // If we don't set a timeout before enabling the Select Interaction again,
        // it will fire with the location if the last point, selecting only Point and/or LineString,
        // not the Polygon. This would clear the selected features. This occurs even if we stop the event propagation.
        setTimeout(() => {
            this._registeredDraw.setActive(true);
            this._registeredSelect.setActive(true);
        });
    }
    return false;
}
WebWanderer
  • 10,380
  • 3
  • 32
  • 51