Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.3.0 - Added events dispatching #3

Merged
merged 1 commit into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,20 @@ map.addInteraction(interaction);

If `waySource` is not provided, `OSMOverpass` will be used as source for snapping, so the constructor options for `OSMWaySnap` will be extended to include [thoses options from `OSMOverpassSourceBase`](https://github.com/ponlawat-w/ol-osmoverpass#constructor-options).

## Events

All event objects dispatched have property `feature` being the active feature of the interaction.

Event types:
- `waysnapstart`: when the interaction starts on a feature whether creation or edition.
- `waysnapstartcreate`: when the interaction has created a new feature.
- `waysnapstartedit`: when the interaction has started edition an existing feature.
- `waysnapupdate`: when the interaction has updated the active feature.
- `waysnapend`: when interaction has finished.

## Examples

[Full page example using the library from CDN](./examples/index.html)
[Examples in HTML using CDN](./examples/index.html)

### Using as module

Expand Down Expand Up @@ -110,8 +121,4 @@ const interaction = new OSMWaySnap({
map.addInteraction(interaction);
```

### Using as CDN

[HTML Example](./examples/index.html)

---
23 changes: 20 additions & 3 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@
</div>
</div>

<div>
<span id="status"></span>
|
<span id="event"></span>
</div>

<div id="status"></div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ol.js"></script>
<script src="https://www.unpkg.com/ol-osmwaysnap/dist/bundle/index.js"></script>
<script lang="js">
Expand Down Expand Up @@ -76,12 +78,27 @@
document.getElementById('status').innerHTML = 'Loading from OSM…';
});
interaction.getWaySource().on('featuresloadend', () => {
document.getElementById('status').innerHTML = '';
document.getElementById('status').innerHTML = 'Idle';
});
interaction.getWaySource().on('featuresloaderror', () => {
document.getElementById('status').innerHTML = 'ERROR';
});

interaction.on('waysnapstartcreate', () => {
document.getElementById('event').innerHTML = 'A new feature has been created.';
});
interaction.on('waysnapstartedit', () => {
document.getElementById('event').innerHTML = 'A feature edition has started.';
});
interaction.on('waysnapupdate', e => {
const n = e.feature.getGeometry().getCoordinates().length;
document.getElementById('event').innerHTML = `The feature has been updated with vertices: ${n}`;
});
interaction.on('waysnapend', e => {
const n = e.feature.getGeometry().getCoordinates().length;
document.getElementById('event').innerHTML = `Feature edition ended with total vertices: ${n}`;
});

const wkt = new ol.format.WKT();
const geojson = new ol.format.GeoJSON();

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ol-osmwaysnap",
"type": "module",
"version": "0.2.0",
"version": "0.3.0",
"description": "OpenLayers Interaction Extension for Snapping Ways from OSM using Overpass API",
"keywords": [
"osm",
Expand Down
55 changes: 55 additions & 0 deletions src/event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Event from 'ol/events/Event';
import type { Feature } from 'ol';
import type { ObjectEvent } from 'ol/Object';
import type { Types as ObjectEventTypes } from 'ol/ObjectEventType';
import type { CombinedOnSignature, EventTypes as ObservableEventTypes, OnSignature } from 'ol/Observable';
import type { LineString } from 'ol/geom';

/**
* Event type for OSMWaySnapInteraction
*/
export const OSMWaySnapEventType = {
/** Event when OSMWaySnap interaction starts on a feature whether creation or edition. */
WAYSNAPSTART: 'waysnapstart',

/** Event when OSMWaySnap interaction has created a new feature. */
WAYSNAPSTARTCREATE: 'waysnapstartcreate',

/** Event when OSMWaySnap has started edition an existing feature. */
WAYSNAPSTARTEDIT: 'waysnapstartedit',

/** Event when OSMWaySnap has updated the active feature. */
WAYSNAPUPDATE: 'waysnapupdate',

/** Event when OSMWaySnap has finished. */
WAYSNAPEND: 'waysnapend'
} as const;

type OSMWaySnapEventType = typeof OSMWaySnapEventType[keyof typeof OSMWaySnapEventType];

/**
* Event for OSMWaySnap interaction
*/
export class OSMWaySnapEvent extends Event {
/** Active feature for the event */
private _feature: Feature<LineString>;

/** Active feature for the event */
public get feature(): Feature<LineString> { return this._feature };

/**
* Constructor
* @param type Type of event
* @param feature Active feature for the event
*/
constructor(type: OSMWaySnapEventType, feature: Feature<LineString>) {
super(type);
this._feature = feature;
}
}

/** Type signature for OSMWaySnap interaction event dispatcher */
export type OSMWaySnapOnSignature<Return> = OnSignature<ObservableEventTypes, Event, Return>
& OnSignature<ObjectEventTypes|'change:active', ObjectEvent, Return>
& OnSignature<OSMWaySnapEventType, OSMWaySnapEvent, Return>
& CombinedOnSignature<ObservableEventTypes|ObjectEventTypes|'change:active'|OSMWaySnapEventType, Return>;
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { OSMWaySnapEventType, OSMWaySnapEvent, type OSMWaySnapOnSignature } from './event';
export { default as OSMWaySnap, type OSMWaySnapOptions } from './interaction';
export { default as LineStringUtils } from './line-string-utils';
62 changes: 57 additions & 5 deletions src/interaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import { Vector as VectorSource } from 'ol/source';
import { Circle, Fill, Stroke } from 'ol/style';
import Style, { createEditingStyle } from 'ol/style/Style';
import { OSMOverpassWaySource, type OSMOverpassSourceOptions } from 'ol-osmoverpass';
import { OSMWaySnapEvent, OSMWaySnapEventType } from './event';
import LineStringUtils from './line-string-utils';
import type { MapBrowserEvent } from 'ol';
import type { EventsKey } from 'ol/events';
import type { Coordinate } from 'ol/coordinate';
import type { StyleLike } from 'ol/style/Style';
import type { MapBrowserEvent } from 'ol';
import type { OSMWaySnapOnSignature } from './event';

/** Options for OSMWaySnap interaction */
type SnapOptions = {
Expand Down Expand Up @@ -47,6 +50,7 @@ type SnapOptionsOSMOverpassWaySource = Omit<SnapOptions, 'waySource'> & {
waySource?: undefined
} & Partial<OSMOverpassSourceOptions>;

/** Options for OSMWaySnap interaction */
export type OSMWaySnapOptions = SnapOptions | SnapOptionsOSMOverpassWaySource;

/**
Expand Down Expand Up @@ -128,6 +132,10 @@ export default class OSMWaySnap extends PointerInteraction {
this.addChangeListener('active', this.activeChanged.bind(this));
}

declare on: OSMWaySnapOnSignature<EventsKey>;
declare once: OSMWaySnapOnSignature<EventsKey>;
declare un: OSMWaySnapOnSignature<EventsKey>;

/**
* Remove the interaction from its current map and attach it to the new map.
* Subclasses may set up event handlers to get notified about changes to
Expand Down Expand Up @@ -172,11 +180,14 @@ export default class OSMWaySnap extends PointerInteraction {
return this.activeFeature;
}

/** Called when the editing is finished, clear all the sketching and candidates. */
/**
* Called when the editing is finished, clear all the sketching and candidates.
*/
public finishEditing() {
if (this.autoFocus && this.map && this.activeFeature && this.coordinates.length > 1) {
this.map.getView().fit(this.activeFeature.getGeometry()!.getExtent(), { padding: Array(4).fill(this.focusPadding) });
}
const feature = this.activeFeature;
this.activeFeature = undefined;
this.coordinates = [];
this.removeSketchPoint();
Expand All @@ -188,6 +199,8 @@ export default class OSMWaySnap extends PointerInteraction {
}
this.draftOriginalLine = undefined;
this.mergingDraftOriginalLine = false;
this.updateOverlayLayer();
this.dispatchEnd(feature);
}

/**
Expand All @@ -204,13 +217,15 @@ export default class OSMWaySnap extends PointerInteraction {
const overlappedFeatures = this.source.getFeatures().filter(f => f.getGeometry()?.intersectsCoordinate(event.coordinate) ?? false);
if (overlappedFeatures.length) {
this.enterEditMode(overlappedFeatures[0], event.coordinate);
this.updateFeature();
this.updateFeature(false);
this.dispatchStartEdit(this.activeFeature!)
return false;
}
}
if (this.allowCreate) {
this.coordinates = [this.sketchPoint?.getGeometry()!.getCoordinates() ?? event.coordinate];
this.updateFeature();
this.updateFeature(false);
this.dispatchStartCreate(this.activeFeature!);
return false;
}
return super.handleEvent(event);
Expand Down Expand Up @@ -241,6 +256,40 @@ export default class OSMWaySnap extends PointerInteraction {
return super.handleMoveEvent(event);
}

/**
* Dispatch start and start create events
* @param feature Feature to dispatch event, otherwise this.activeFeature!
*/
protected dispatchStartCreate(feature?: Feature<LineString>) {
this.dispatchEvent(new OSMWaySnapEvent(OSMWaySnapEventType.WAYSNAPSTART, feature ?? this.activeFeature!));
this.dispatchEvent(new OSMWaySnapEvent(OSMWaySnapEventType.WAYSNAPSTARTCREATE, feature ?? this.activeFeature!));
}

/**
* Dispatch start and start edit events
* @param feature Feature to dispatch event, otherwise this.activeFeature!
*/
protected dispatchStartEdit(feature?: Feature<LineString>) {
this.dispatchEvent(new OSMWaySnapEvent(OSMWaySnapEventType.WAYSNAPSTART, feature ?? this.activeFeature!));
this.dispatchEvent(new OSMWaySnapEvent(OSMWaySnapEventType.WAYSNAPSTARTEDIT, feature ?? this.activeFeature!));
}

/**
* Dispatch update event
* @param feature Feature to dispatch event, otherwise this.activeFeature!
*/
protected dispatchUpdate(feature?: Feature<LineString>) {
this.dispatchEvent(new OSMWaySnapEvent(OSMWaySnapEventType.WAYSNAPUPDATE, feature ?? this.activeFeature!));
}

/**
* Disptach end event
* @param feature Feature to dispatch event, otherwise this.activeFeature!
*/
protected dispatchEnd(feature?: Feature<LineString>) {
this.dispatchEvent(new OSMWaySnapEvent(OSMWaySnapEventType.WAYSNAPEND, feature ?? this.activeFeature!));
}

/**
* Start editing on the selected feature
* @param feature Feature to edit
Expand All @@ -264,8 +313,9 @@ export default class OSMWaySnap extends PointerInteraction {

/**
* Create or update active feature from editing coordinates.
* @param dispatch True to dipatch waysnapupdate event
*/
private updateFeature() {
private updateFeature(dispatch: boolean = true) {
if (!this.activeFeature) {
this.activeFeature = new Feature<LineString>(new LineString(this.coordinates));
} else {
Expand All @@ -274,6 +324,7 @@ export default class OSMWaySnap extends PointerInteraction {
this.source.addFeature(this.activeFeature);
}
}
dispatch && this.dispatchUpdate();
this.calculateCandidates();
}

Expand Down Expand Up @@ -398,6 +449,7 @@ export default class OSMWaySnap extends PointerInteraction {
* @param coordinates Original sketch line coordinates to get extended
*/
private extendSketchLineCoorsToDraftOriginal(coordinates: Coordinate[]) {
this.mergingDraftOriginalLine = false;
const lastCoordinate = coordinates[coordinates.length - 1];
if (!this.draftOriginalLine || !this.draftOriginalLine.getGeometry()!.intersectsCoordinate(lastCoordinate)) return;
let lineToExtend = this.draftOriginalLine.getGeometry()!;
Expand Down
14 changes: 7 additions & 7 deletions tests/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import type { OSMWaySnap } from '../dist';
/**
* Drawing a linestring network that looks more or less like this:
*
* │
* ┌─┴─┐
* │ │
* └─┬─┘
* ──┼──
* │
* │
* │ => fLoopExtended
* ┌─┴─┐ => fLoop
* │ │ => fLoop
* └─┬─┘ => fCenterToBeforeLoop
* ──┼── => fCenterTurnLeft / fCenterTurnRight
* │ => fDownToCenter
* │ => fDownToCenter
* @returns vector source
*/
export const getDefaultWaySource = (): VectorSource<Feature<LineString>> => {
Expand Down
47 changes: 46 additions & 1 deletion tests/interaction.create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import { Projection } from 'ol/proj';
import { getDefaultWaySource, mouseClick, mouseMove } from './common';
import { OSMWaySnap } from '../dist';
import { OSMWaySnap, OSMWaySnapEventType } from '../dist';
import { Feature } from 'ol';
import { LineString, Point } from 'ol/geom';

Expand Down Expand Up @@ -317,4 +317,49 @@ describe('Test OSMWaySnap Interaction: Line Creation', () => {
expect(coors[1]).toEqual([25, 25]);
expect(coors[2]).toEqual([50, 25]);
});

it('fires events when start creating and finish creating features', () => {
let waySnapStart = false;
let waySnapStartCreate = false;
let waySnapStartEdit = false;
let waySnapUpdate = false;
let waySnapEnd = false;
let startFeature: Feature<LineString>|undefined = undefined;
let startCreateFeature: Feature<LineString>|undefined = undefined;
let startEditFeature: Feature<LineString>|undefined = undefined;
let updateFeature: Feature<LineString>|undefined = undefined;
let endFeature: Feature<LineString>|undefined = undefined;
interaction.on(OSMWaySnapEventType.WAYSNAPSTART, e => { waySnapStart = true; startFeature = e.feature; });
interaction.on(OSMWaySnapEventType.WAYSNAPSTARTCREATE, e => { waySnapStartCreate = true; startCreateFeature = e.feature; });
interaction.on(OSMWaySnapEventType.WAYSNAPSTARTEDIT, e => { waySnapStartEdit = true; startEditFeature = e.feature; });
interaction.on(OSMWaySnapEventType.WAYSNAPUPDATE, e => { waySnapUpdate = true; updateFeature = e.feature; });
interaction.on(OSMWaySnapEventType.WAYSNAPEND, e => { waySnapEnd = true; endFeature = e.feature; });

mouseMove(map, interaction, [0, 0]);
mouseClick(map, interaction, [0, 0]);

expect(waySnapStart).toBeTruthy();
expect(waySnapStartCreate).toBeTruthy();
expect(waySnapStartEdit).toBeFalsy();
expect(waySnapUpdate).toBeFalsy();
expect(waySnapEnd).toBeFalsy();
expect(startFeature).toBeDefined();
expect(startCreateFeature).toBeDefined();
expect(startEditFeature).toBeUndefined();
expect(updateFeature).toBeUndefined();
expect(endFeature).toBeUndefined();
expect(startCreateFeature).toBe(startFeature);

mouseMove(map, interaction, [50, 25]);
mouseClick(map, interaction, [50, 25]);

expect(waySnapUpdate).toBeTruthy();
expect(waySnapEnd).toBeFalsy();
expect(updateFeature).toBe(startCreateFeature);

mouseClick(map, interaction, [50, 25]);

expect(waySnapEnd).toBeTruthy();
expect(endFeature).toBe(startCreateFeature);
});
});
Loading